Click here to Skip to main content
Licence 
First Posted 29 Jan 2002
Views 326,760
Bookmarked 37 times

Making your C++ code thread-safe

By | 29 Jan 2002 | Article
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 <process.h>
#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

About the Author

Xiangyang Liu 刘向阳



United States United States

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionError: Cannot convert 'void**' to 'long int*' for argument 1 PinmemberKells Bells5:33 28 Mar '07  
AnswerRe: Error: Cannot convert 'void**' to 'long int*' for argument 1 Pinmembercioba_c5:07 28 Jul '09  
GeneralGlobal Variables and Statics Pinmemberdshah6:41 18 Oct '03  
GeneralRe: Global Variables and Statics PinmemberXiangyang Liu9:13 18 Oct '03  
GeneralTime Pinmemberwoodman900011:19 15 Sep '03  
GeneralRe: Time PinmemberXiangyang Liu22:05 15 Sep '03  
Generalm_cs with copy constructor Pinmembergoodgeokeke1:35 2 Apr '03  
GeneralAnalysis of the two PinmemberTim Smith3:56 31 Jan '02  
GeneralRe: Analysis of the two PinmemberXiangYangLiu4:51 31 Jan '02  
GeneralRe: Analysis of the two PinmemberTim Smith12:29 31 Jan '02  
GeneralRe: Analysis of the two PinmemberTim Smith12:36 31 Jan '02  
GeneralRe: Analysis of the two PinmemberXiangYangLiu1:55 1 Feb '02  
GeneralRe: Analysis of the two PinmemberTim Smith2:25 1 Feb '02  
GeneralRe: Analysis of the two PinmemberXiangYangLiu4:13 1 Feb '02  
GeneralRe: Analysis of the two PinmemberTim Smith11:05 1 Feb '02  
GeneralRe: Analysis of the two PinmemberWilliam E. Kempf4:56 1 Feb '02  
GeneralRe: Analysis of the two PinmemberXiangYangLiu5:25 1 Feb '02  
GeneralThread starvation PinmemberAnonymous26:33 30 Jan '02  
GeneralRe: Thread starvation PinmemberXiangYangLiu6:41 30 Jan '02  
GeneralRe: Thread starvation PinmemberAnonymous26:57 30 Jan '02  
GeneralRe: Thread starvation PinmemberTim Smith7:27 30 Jan '02  
GeneralA very interesting comparison PinmemberXiangYangLiu6:14 30 Jan '02  
GeneralRe: A very interesting comparison PinmemberTim Smith7:25 30 Jan '02  
GeneralRe: A very interesting comparison PinmemberXiangyang Liu7:49 30 Jan '02  
GeneralRe: A very interesting comparison PinmemberWilliam E. Kempf9:13 30 Jan '02  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120517.1 | Last Updated 30 Jan 2002
Article Copyright 2002 by Xiangyang Liu 刘向阳
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid