WindowLib Threading

This page describes a couple of simple classes I added to the C++ Win32 Library to deal with multiple threads. The library existed far too long without being thread safe, but when I wanted to add some additional threads to the next version of my Mancala game I knew I could put this off no longer.

The Approach

Concepts

Win32 provides several different methods of managing threads and the way that they access shared resources, such as a static or global variable. These include critical sections, mutexes, semaphores, and events. Of these, critical sections are the simplest and fastest, so I chose them to make my library thread safe.

An application uses a critical section by first declaring a CRITICAL_SECTION variable that can then be locked and unlocked as needed to prevent multiple threads from executing in a certain section of code. When a thread needs to operate on a shared resource, the code dealing with that resource is bounded by calls to EnterCriticalSection and LeaveCriticalSection.

// This is a globally-accessible critical section variable
CRITICAL_SECTION cs;

int main()
{
    // All critical sections must be initialized prior to use
    ::InitializeCriticalSection(&cs);

    // Program may create and start a few threads here.

    // Critical sections are deleted before program termination
    ::DeleteCriticalSection(&cs);

    return 0;
}

DWORD WINAPI ThisIsAThread(LPVOID p)
{
    // Do some generic independent processing until 
    // a shared resource needs to be manipulated

    ::EnterCriticalSection(&cs);
    // Only one thread at a time may execute the code between the 
    // calls to enter and leave. Here a shared resource may be 
    // safely manipulated.
    ::LeaveCriticalSection(&cs);

    // Continue independent processing, then exit
    return 0;
}

This is a simple example, but in a real application the housekeeping chores can add up. Since I'm using C++ for my library, I can take advantage of a very useful idiom I first saw described in The C++ Programming Language.

Resource Acquisition Is Initialization

The resource acquisition is initialization idiom describes a way to let the semantics of the language take care of the details of acquiring and releasing resources such as critical sections or other objects that must pair an acquisition with a release.

Look again at the example above. What happens when an exception is thrown after the call to EnterCriticalSection but before LeaveCriticalSection? What if a critical section is created (or worse, dozens or hundreds are created) without being deleted? Whatever you can imagine, it's that bad or worse.

The RAII idiom delgates acquisition and release to a class that performs acquisition when it is instantiated and performs release when destroyed. In other words, the class constructor gains access to a resource (such as a lock on a critical section). When the object is destroyed, (when it goes out of scope, for example) the destructor releases the resource that was acquired in the constructor.

The CriticalSection Class

Here is the CriticalSection class that I use in my library to manage the lifetime of a CRITICAL_SECTION variable.

class CriticalSection
{
public:
    CriticalSection()
    {
        ::InitializeCriticalSection(&cs);
    }

    ~CriticalSection()
    {
        ::DeleteCriticalSection(&cs);
    }

    void Enter()
    {
        ::EnterCriticalSection(&cs);
    }

    void Leave()
    {
        ::LeaveCriticalSection(&cs);
    }

private:
    CRITICAL_SECTION cs;
};

The class contains a CRITICAL_SECTION that is initialized in the constructor and deleted in the constructor. The Enter and Leave methods may be called on an instance of this class to control thread access based on the critical section object it contains. Here's how it would be used to simplify the first example:

// Replace CRITICAL_SECTION with an instance of CriticalSection
CriticalSection csObject;

int main()
{
    // Program may create and start a few threads here.

    return 0;
}

DWORD WINAPI ThisIsAThread(LPVOID p)
{
    // Do some generic independent processing until 
    // a shared resource needs to be manipulated

    csObject.Enter();
    // Only one thread at a time may execute the code between the 
    // calls to enter and leave. Here a shared resource may be 
    // safely manipulated.
    csObject.Leave();

    // Continue independent processing, then exit
    return 0;
}

This example looks like I forgot to initialize and delete the critical section, but it happens automatically because the csObject constructor executes before the program enters main, and the destructor executes after main is finished. This is great, but we still have to deal with the problem of abnormal thread termination while a critical section is locked.

The Lock Class

The application of RAII to the initialization and deletion of the critical section was so elegant, why don't we apply it to entering and leaving the critical section as well?

class Lock
{
public:
    Lock(CriticalSection& cs) : cs(cs)
    {
        cs.Enter();
    }

    ~Lock()
    {
        cs.Leave();
    }

private:
    CriticalSection& cs;

    // A developer is not allowed to create a standalone Lock 
    // object, so this static satisfies the compiler when the 
    // default constructor is made private.
    static CriticalSection dummy;
    Lock() : cs(dummy) {}
};

Now it becomes very easy to safely enter and leave a critical section. Just instantiate a Lock object with a reference to an existing CriticalSection object. The critical section will be acquired in the constructor, and it will be released in the destructor when the Lock object goes out of scope, regardless of the manner by which scope was exited.

// Replace CRITICAL_SECTION with an instance of CriticalSection
CriticalSection csObject;

int main()
{
    // Program may create and start a few threads here.

    return 0;
}

DWORD WINAPI ThisIsAThread(LPVOID p)
{
    // Do some generic independent processing until 
    // a shared resource needs to be manipulated

    // Wrap the thread-safe code in a scope
    {
        // Creation occurs when execution enters the scope
        Lock lock(csObject);

        // Shared resource is manipulated.
    }

    // The lock is released automatically when execution leaves the scope

    return 0;
}

As you can see, RAII is a very powerful idiom. It is also commonly used in smart pointers (such as std::auto_ptr) or other classes that manage resources (such as BeginPaint in WindowLib). Whenever you find yourself trying to match resource acquisitions with releases, consider applying this idiom.

The Implementation

I have been working on a set of templates that implement the RAII approach while giving me more flexibility to control the synchronization and locking mechanisms. What I came up with is a policy-based approach that lets me create locks such as those in the following example.

// This class uses a critical section synchronization mechanism.
// The typedef makes locking a little cleaner
typedef Synch<CriticalSection> SynchA;
SynchA sA;

void fA()
{
    SynchA::Lock lock(sA);
    // this code is now inside a critical section
}


// This class uses a mutex synchronization mechanism.
typedef Synch<Mutex> SynchB;
SynchB sB;

void fB()
{
    SynchB::Lock lock(sB);
    // this code is preceded by a WaitForSingleObject call
}


// Mechanism-specific policies give me additional flexiblity,
// like setting a spin count on a critical section
typedef Synch<CriticalSection, CriticalSectionPolicy<5000> > SynchC;
SynchC sC;

// I can also use policies for mutexes to set options such as security 
// attributes and names
typedef Synch<Mutex, MutexPolicy<NULL /*security att.*/, false /*init owner*/, "Sample Mutex Name"> > SynchD;
SynchD sD;


// The MultiLock class lets me wait for multiple synchronization objects
typedef Synch<Mutex> SynchE;
SynchE sE1;
SynchE sE2;

void fE()
{
    SynchE::MultiLock multiLock;
    multiLock.Add(sE1);
    multiLock.Add(sE2);
    multiLock.Lock();

    // this code is preceded by a WaitForMultipleObjects call
}

// Locking policies provide finer control over locks, but require me to be more 
// explicit with the locking class.

void fF()
{
    MultiLock<Mutex, MutexPolicy<true /*wait for all*/, 5000 /*timeout*/> > multiLock;
    multiLock.Add(sB);
    multiLock.Add(sD);
    multiLock.Add(sE1);
    multiLock.Add(sE2);
    multiLock.Lock();

    // this code is preceded by a WaitForMultipleObjects call
}

As you can see, so far I get the ability to use either critical sections or mutexes with the same core set of templates.

I still haven't uploaded the new version of the library, but the Threading.h is available for review and comment.