Contents

  Threads
  Race Conditions
  Locking
  Dead Locks
  Live Locks
  Starvation
  Affinity
Threads - Locking

Now that we have discovered one of the pitfalls of multi-threaded code, namely Race Conditions, we need to see how to combat them. In plain English, the solution is simple, only let one thread work with a resource at any one time. But how do we do that in code? I will present C# and Java first and then reserve a section for Win32.

C# and Java both use an Object (or any derived type) as a lock, and then use one or more block of code around that lock to ensure exclusive access to those blocks of code. Inside those blocks of code they then access and change the resource. This is best illustrated with an example.

C#
    int g_counter = 0;
    readonly Object r_LockA = new Object(); 
    
    void IncrementCounter()
    {
        lock(r_LockA)
        {
            g_counter++;
        }    
    }
    
    void DecrementCounter()
    {
        lock(r_LockA)
        {
            g_counter--;
        }    
    }


Java
    int g_counter = 0;
    final Object r_LockA = new Object(); 
    
    void IncrementCounter()
    {
        lock(r_LockA)
        {
            g_counter++;
        }    
    }
    
    void DecrementCounter()
    {
        synchronized(r_LockA)
        {
            g_counter--;
        }    
    }
I have included a "dummy" function called DecrementCounter() here just to show that many blocks of code can/are controlled by the same "lock" for a resource. I.E. if one thread is inside a block of code protected by lock "A" then no other threads can be in ANY block of code protected by lock "A". Also, you mustn't have the resource (g_counter in this case) changed in one place inside the lock only to have another function change # it without a lock. The rule of thumb is: If you lock on it in one place, you lock on it in all places.

The relevant language then ensures that only one thread can be executing that block of code at any one time, ensuring (if you have got all access to the resource inside blocks protected by the same lock) exclusive access to the resource.

Win32 is far more complicated, but I will keep the discussion condensed to to Mutexes. (Readers are encouraged to investigate Critical Sections and Semaphores). A Mutex (stands for Mutual Exclusion) is a kernal object so will survive a thread and/or a process going down. Therefore you need to take extra care to release it when you are done.

We also use the known advantages of C++ destructors being guaranteed and exception safe, and create 2 classes, namely Mutex and Lock.

Win32
    class Mutex
    {
    public:
        Mutex();
        ~Mutex();
        void Enter();
        void Exit();
    private:
        HANDLE _hMutex;
    }
    
    Mutex::Mutex()
    {
        _hMutex = CreateMutex(
                    NULL,                      // lpSecurityAttributes - Ignoring Security
                    TRUE                       // bInitialOwner        - We own it
                    "MyMutextWithUniqueName"); // lpName               - Unique Name
    }
    
    Mutex::~Mutex()
    {
        CloseHandle(_hMutex);
    }
    
    Mutex::Enter()
    {
        WaitForSingleObject(_hMutex, INFINITE);
    }
    
    Mutex::Exit()
    {
        ReleaseMutex(_hMutex);
    }
This class is used to house the Win32 Mutex. We then use the Lock class to be able to use it

Win32
    class Lock
    {
    public:
        Lock(Mutex* pMutex);
        ~Lock();
    private:
        Mutex* _pMutex;
    }
    
    Lock::Lock(Mutex* pMutex)
    {
        _pMutex = pMutex;
        _pMutex->Enter();
    }
    
    Mutex::~Mutex()
    {
        _pMutex->Exit();
    }
These classes together then make it easy to use:

Win32
    class Test
    {
    public:
        void IncrementCounter();
        void DecrementCounter();
    private:
        int   _counter;
        Mutex _mutex;     
    }

    Test::IncrementCounter()
    {
        Lock lock(&_mutex);
        _counter++
    }
    
    Test::DecrementCounter()
    {
        Lock lock(&_mutex);
        _counter++
    }
The nice thing about using the lock class it it acquires the lock on declaration, and releases it automatically after the funtion has completed because the Lock classes' destructor is called by the C++ implementation.

<< Prev Next >>


 
 

Privacy Policy
©2008 DebugInspector. All rights reserved