Feb 9, 2011

Thread Synchronization in Windows using Critical Section

In multi threaded environments, where more than one thread operates on the shared data (global variables, collections) the results can be unpredictable due to race conditions. Critical Section is a mechanism that ensures that only one thread executes a particular piece of the code. A thread, once it enters a critical section should not be interrupted. Critical Sections have the advantage of not being kernel objects and are maintained in user space. This usually, but not always, provides performance improvements. The CRITICAL_SECTION data type is basically a structure whose fields are used only internally to Windows

Windows provide four functions for using critical sections. To use these functions, you must define an object of type CRITICAL_SECTION.

Key Points

1. Define a CRITICAL_SECTION object cs
2. Initialize a Critical section using InitializeCriticalSection (&cs);
3. After the critical section object has been initialized, a thread enters the critical section by calling EnterCriticalSection(&cs); No two threads can own the critical section at the same time
4. A thread leaves the critical section by calling LeaveCriticalSection (&cs);
5. When the critical section object is no longer needed, it can be deleted by the program by calling DeleteCriticalSection (&cs);
#ifndef _Lock_H_
#define _Lock_H_

#include <windows.h>

/**
*@description: A simple Lock implementation using windows critical section object
*/

namespace examples
{
    class Lock
    {
    public:
        Lock()
        {
            ::InitializeCriticalSection(&m_cs);
        }

        ~Lock()
        {
            ::DeleteCriticalSection(&m_cs);
        }

        void acquire()
        {
            ::EnterCriticalSection(&m_cs);
        }

        void release()
        {
            ::LeaveCriticalSection(&m_cs);
        }

    private:
        Lock(const Lock&);
        Lock& operator=(const Lock&);

        CRITICAL_SECTION m_cs;
    };

}

#endif //_Lock_H_


The program below uses Critical Section mechanism to ensure that the two threads (add thread and sub thread) operations are consistent on the shared global variable
#include <windows.h>
#include <process.h>
#include <iostream>
#include <assert.h>
#include "Lock.h"

//File: CriticalSectionExample.cpp

#define MAX_THREADS 2

using namespace std;

static unsigned int counter = 100;
static bool alive = true;
static examples::Lock lock;

static unsigned __stdcall sub(void *args)
{
    while(alive)
    {
        lock.acquire();
        cout << "[Sub(" << counter << ")]" << endl;
        counter -= 10;
        lock.release();
        ::Sleep(500);
    }
    return 0;
}

static unsigned __stdcall add(void *args)
{
    while(alive)
    {
        lock.acquire();
        cout << "[Add(" << counter << ")]" << endl;
        counter += 10;
        lock.release();
        ::Sleep(500);
    }
    return 0;
}

int main()
{
    // create threads
    unsigned tadd;
    HANDLE hadd = (HANDLE) ::_beginthreadex(0, 0, &add, 0, CREATE_SUSPENDED, &tadd);
    assert(hadd != 0);

    unsigned tsub;
    HANDLE hsub = (HANDLE) ::_beginthreadex(0, 0, &sub, 0, CREATE_SUSPENDED, &tsub);
    assert(hsub != 0);

    // start threads
    ::ResumeThread(hadd);
    ::ResumeThread(hsub);

    ::Sleep(10000); // let threads run for 10 seconds

    // stop & cleanup threads
    alive = false;
    ::WaitForSingleObject(hsub, INFINITE);
    ::CloseHandle(hsub);
    ::WaitForSingleObject(hadd, INFINITE);
    ::CloseHandle(hadd);

    return 0;
}

Sample Run of the Program:

If you want to dig deep into the Windows Critical Sections, refer Matt Pietrek and Russ Osterlund article on Debugging Code Deadlocks in Critical Sections Under Windows

No comments :

Post a Comment