Condividi tramite


Best method for Critical Section management in C++

When it comes down to using critical sections in your C++ code you have pretty much standard options. Simply call "EnterCriticalSection" at the start of your function and call "LeaveCriticalSection" at the end of your function. No doubt things will work fine, but this approach is not going to be very scalable. Look at the following example:

 void DummyFunction()
{
 EnterCriticalSection( &m_cs );
 
 if( m_count == 0 )
 {
 // since we are returning early leave the critical section
 LeaveCriticalSection( &m_cs );
 return;
 }
 
 // do some dummy work here
 m_count = m_count + 1;
 CallingSomeDummyFunction();
 
 LeaveCriticalSection( &m_cs );
}

As you can see this solution is not very scalable, as every time you have to return early you will have to call "LeaveCriticalSection" otherwise you will deadlock. A very bad workaround that some developers use is to use "goto" statements. Look at the following example:

 void DummyFunction()
{
 EnterCriticalSection( &m_cs );
 
 if( m_count == 0 )
 {
 goto done:
 }
 
 // do some dummy work here
 m_count = m_count + 1;
 CallingSomeDummyFunction();
 
done:
 LeaveCriticalSection( &m_cs );
}

This will simplify things because now you do not have to call "LeaveCriticalSection" every time you want to return early, but on the other hand you will be stuck with jumps to prehistoric-goto statements. Also this will make your code hard to follow.

Modern day solution to this problem will be to use a class that will manage the ownership of critical section (full source code of CriticalSectionOwner below).

 void DummyFunction()
{
 CriticalSectionOwner csOwner( m_cs );
 
 if( m_count == 0 )
 return;
 
 // do some dummy work here
 m_count = m_count + 1;
 CallingSomeDummyFunction();
}

If you use "CriticalSectionOwner" wrapper class then you will not have to worry about cleanup-when-returning-early, cleanup-when-exceptions-happen, returning-values-from-your function. You can even write a small macro around this class so that you do not have to declare a variable every time. For example:

 bool DummyFunction()
{
 CRITICAL_SECTION_OWNER( m_cs );
 
 if( m_count == 0 )
 return false;
 
 // do some dummy work here
 m_count = m_count + 1;
 CallingSomeDummyFunction();
 return true;
}

This will keep things simple and neat.

 

Full Source Code of CriticalSectionOwner:

 #pragma once
 /******************************************************************************
 *
 * Author: Asim Goheer
 *
 * CriticalSectionOwner is a very lightweight helper class to make critical
 * section management easier. 
 *
 * This helper will guarantee proper cleanup, becase in case of an exception
 * it's destructor will run thus releasing critical section.
 * 
 * This helper will also allow you to return early from a function. Because
 * when you return early from a function the destructor will automatically
 * release critical section.
 *
 ******************************************************************************/
 
 class CriticalSectionOwner
 {
 public:
 // constructor
 CriticalSectionOwner( CRITICAL_SECTION& cs ) : m_cs( cs )
 {
 EnterCriticalSection( &m_cs );
 }
 
 // destructor
 ~CriticalSectionOwner()
 {
 LeaveCriticalSection( &m_cs );
 }
 
 private:
 // Disable copy constructor
 CriticalSectionOwner( const CriticalSectionOwner& ) = delete;
 
 private:
 CRITICAL_SECTION& m_cs;
 };
 
 // Define a macro that we can use
 #define CRITICAL_SECTION_OWNER( x ) CriticalSectionOwner csOwner( x );