Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Making your C++ code thread-safe

0.00/5 (No votes)
29 Jan 2002 1  
A simple C++ critical section implementation for the Win32 platforms

When writing multithreaded C++ programs on WIN32 platforms, you need to protect certain shared objects so that only one thread can access them at any given time. You can use 5 system functions to achieve this. They are InitializeCriticalSection, EnterCriticalSection, TryEnterCriticalSection, LeaveCriticalSection, and DeleteCriticalSection.
In general, these functions are fairly easy to use. You need to declare a CRITICAL_SECTION variable for a shared object in your program and call the InitializeCriticalSection function to initialize it. Next, call EnterCriticalSection before accessing the shared object and call LeaveCriticalSection when you are done with the object. The DeleteCriticalSection function is called to release all system resources associated with the critical section. Of course, you can also use the critical section classes in MFC and ATL.

I am wondering why do we need "system" resources to implement critical section? What types of "system" resources are we talking about? Unfortunately, even after searching MSDN, I still did not find the answers. Since I am used to doing things my own way, I came up with my own implementation, which may have some advantages. For example, there is no need to initialize or release any system resource. My implementation works on Windows 98 or later and Windows NT 4.0 or later (on the other hand, the TryEnterCriticalSection system function is not supported on Windows 98). After some testing, I found that my implementation seems to be (or should I say, probably? maybe?) more efficient than using the system functions, please see my comment 'A very interesting comparison' listed after the article.

Here is my XYCriticalSection class. It does not depend on any library (MFC, ATL, STL, etc.). However, the idea is not exactly new.

#ifndef XYCRITICALSECTION_H
#define XYCRITICALSECTION_H
#include "windows.h"


class XYCriticalSection
{
    long m_nLockCount;
    long m_nThreadId;
    bool SetLock(constlong nThreadId);

public:
    XYCriticalSection()
    {
        m_nThreadId = 0;
        m_nLockCount = 0;
    }

    void Enter();
    void Leave();
    bool Try();
};
#endif // XYCRITICALSECTION_H

As you can see, there are only 3 public methods in the XYCriticalSection class - Enter, Leave, and Try. The Enter method will block as long as some other thread is in the critical section. The Try method is non-blocking. It returns true if the critical section is entered successfully, otherwise it returns false. The Leave method must be called once for each call of Enter and each successful call of Try.

To use this class in your program, just declare an instance of XYCriticalSection for each shared object you want to protect. Call Enter before accessing the object and call Leave after you are done with it. There is no system resource to initialize or release. Please note that the same thread can enter a critical section multiple times without blocking itself.

One inconvenience with this implementation is that you have to make sure that your thread calls the Leave method after entering the critical section, otherwise the critical section will be locked and no other thread can enter. What can happen is, even if the calls to Enter and Leave are paired in your code, some unexpected exception will cause the thread to skip the Leave method causing a shared object to be locked forever. I wrote another class, XYLock, to help with this situation. All you have to do is declare an XYLock variable using a pointer to the XYCriticalSection instance associated with the shared object. The constructor of XYLock will call XYCriticalSection::Enter and the destructor will call XYCriticalSection::Leave. So there is no way you will forget to call Leave!. Here is the XYLock class.

#ifndef XYLOCK_H
#define XYLOCK_H
#include "XYCriticalSection.h"


class XYLock
{
    XYCriticalSection* m_pCS;

public:
    XYLock(XYCriticalSection* pCS)
    {
        m_pCS = pCS;
        if(m_pCS)
            m_pCS->Enter();
    }

    ~XYLock()
    {
        if(m_pCS)
            m_pCS->Leave();
    }
};
#endif //XYLOCK_H

When an XYLock variable goes out of scope, the lock on the corresponding critical section will be released automatically. Let's see a simple example of defining a thread-safe C++ object using an XYLock object.

#ifndef SAFEOBJ_H
#define SAFEOBJ_H
#include "XYLock.h"


class SafeObj
{
     XYCriticalSection m_cs;
     
public:
    void SafeMethod()
    {
        XYLock myLock(&m_cs);
        //add code to implement the method ...

    }
};
//SAFEOBJ_H

The code in the public member function SafeMethod following the declaration of the myLock variable can only be executed by one thread at any given time. Please note that an XYLock variable should only be declared on the stack (at the beginning of the code block that accesses the shared object). Here is a multithreaded C++ program that further demonstrates the use of XYLock.

#include <stdio.h>

#include 

#include "XYLock.h"


XYCriticalSection myCriticalSection;

void ThreadProc(void *dummy)
{
    while(true)
    {
        try
        {
            XYLock myLock(&myCriticalSection);
            printf("Thread '%X' has the lock\n",::GetCurrentThreadId());
            throw "a test";
        }
        catch(char* p)
        {
            printf("Caught: %s\n",p);
        }
        ::Sleep(1000);
    }
}

void main()
{
    for(int i=0;i<100;i++)
    {
        if(_beginthread(ThreadProc,0,NULL)==-1)
        {
            printf("Failed to create a thread\n");
            return;
        }
    }

    while(true)
    {
        try
        {
            XYLock myLock(&myCriticalSection);
            printf("The main thread '%X' has the lock\n",::GetCurrentThreadId());
            throw "a test from the main thread";
        }
        catch(char* p) 
        {
            printf("Caught: %s\n",p); 
        } 
        ::Sleep(1000); 
    } 
} 

The above code declares a global XYCriticalSection variable to be associated with a shared object which you want to protect from simultaneous accesses by multiple threads. The main function will create 100 threads that forever enter and leave the same critical section. The id of the current thread that owns the critical section is printed to the console window.

Thanks for reading my articles.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here