Click here to Skip to main content
Click here to Skip to main content

MFC Thread Synchronization using SYNCHRONIZED macro

, 7 Jul 2003
Rate this:
Please Sign up or sign in to vote.
Creation of SYNCHRONIZED macro for easy synchronization.

Introduction

VC++ does not have an easy to use synchronized block like that of Java. But it is easier to simulate a macro which will work exactly like the synchronized block of Java. MFC provides classes for synchronization of threads. They are CSyncObject, CCriticalSection, CMutex, CEvent, CSemaphore, CSingleLock and CMultiLock. CSyncObject is the base class for all the other synchronization classes. All the classes implement the two important functions of CSyncObject viz. Lock() and Unlock().

Who can read this article

Dear reader, you should know how to create threads using MFC (yes using AfxBeginThread or CWinThread directly)

Why synchronization

If two threads try to access the same data it may cause a fatal error. For example one thread may try to write into a file, while at the same time some other thread may try to read the data which was never written!! Only after the file is written, it should allow the other thread to read through some inter-thread communication. So to avoid the simultaneous access of data or parts of code, the threads should be protected by a mechanism, which allows only one thread to access the part of code or data. The data or code which is subject to multiple threads simultaneously is called a Critical Section.

What this article tries to do?

This article tries to simulate a synchronized block. The code or data inside the synchronized block ensures that only a single thread can access. The block also provides a way to wait till a flag is set by another thread which is termed as thread communication. Automatic release of the lock gained is ensured by the synchronized block using a technique called Resource Acquisition Is Initialization. The lock is released even if an exception is raised in the middle of the block.

What this article does not try to explain?

This article does not try to explain creation of threads. It just assumes you know how to create threads and face a problem in synchronization. It does not try to explain about deadlocks.

How MFC provides synchronization

A MFC CCriticalSection provides an encapsulation way to protect the critical sections of code or data. A thread before entering (accessing) into the critical section calls the Lock() member function on CCriticalSection, does the delicate process and leaves (releases) the critical section by calling Unlock() member function. If already the the CCriticalSection is locked by another thread say thread2, then the thread calling Lock() say thread1 will wait (without occupying the CPU cycles) until thread2 finishes its work on critical section and calls Unlock(). Then the thread1 is made eligible for entering the critical section.

Advantages of using SYNCHRONIZED block

Using MFC synchronization block we can synchronize code from corruption by multiple threads. But we may try to gain a lock and may not release the lock properly or we may try to access the critical sections of code without gaining a lock. The code which we may use to synchronize may not be legible. Using synchronized block is legible. Further it ensures that the lock is released properly even under abnormal exceptions using Resource Acquisition Is Initialization. The real advantage of synchronized block is it makes thread communication easier by using a function called Wait!!

Use of Resource Acquisition Is Initialization to release the Lock properly

Let us study the following example:

     //without using Resource Acquisition Is Initialization
     void some_func()
     {
        //allocate memory
        char *arr = new char[1024 * 5]; //5 kb is allocated
        //some code which may raise an exception
        //use the array
        //deallocate memory
        delete[] arr;        
     }

In the above sample code we saw how in case of an exception the memory will not be released properly which will result in memory leak. If there are no failures there will be no memory leaks. But there is a way by which the memory will get released in spite of exceptions. This can be achieved using a wrapper often called a smart pointer class which will take care of releasing the resource (memory, file, socket, handles ...) properly. See the following code:

    class CSmartCharArray
    {
      private:
        char *ptr;
      public:
        //store the starting address of the array in data member ptr
        CSmartCharArray(char *addr) : ptr(offset) {}
        //provide a conversion operator to char * for easy access
        operator char*() { return ptr; }
        //release the ptr when destructor is called
        virtual ~CSmartCharArray()
        { 
          delete ptr; 
          ptr = 0; //reset the ptr to point to null 
        }
    };
    ...
    ...
    ...
     //using Resource Acquisition Is Initialization
     void some_func()
     {
        //allocate memory
        CSmartCharArray arr(new char[1024 * 5]); //5 kb is allocated
        //some code which may raise an exception
        //use the array
     }

In the above code the variable arr is declared inside the function some_func() and it's scope will be over when the control from the function is transferred (normally or abnormally). So it's destructor will always be called which will release the array created on the heap!!

To release the Lock() at the end of the function call in case of failure due to exceptions, the CSyncObject is usually wrapped using a CSingleLock object. The CSingleLock object is created on the stack (not a pointer). The destructor of CSingleLock gets invoked when stack is unwound always (because of normal end of the block scope or due to some exception). The destructor of CSingleLock calls the Unlock() method on its wrapped sync object and thus always the lock is released (See Resource Acquisition Is Initialization). To make use of this technique we create a simple class derived from CSingleLock called CSynchronizer.

Simulating SYNCHRONIZED

//Synchronizer.h 
#ifndef SYNCHRONIZED_BLOCK_MFC
#define SYNCHRONIZED_BLOCK_MFC
#include <afxmt.h>
#define SYNCHRONIZED(slock, cs) if(CSynchronizer slock = CSynchronizer(cs))

class CSynchronizer : public CSingleLock  
{
public:
    CSynchronizer(CSyncObject *syncobj) : CSingleLock(syncobj, TRUE) {}
    operator BOOL() { return CSingleLock::IsLocked(); }
    BOOL Wait(DWORD dwTimeOut = INFINITE);
    BOOL Notify();
    virtual ~CSynchronizer() {}

};
#endif

//Synchronizer.cpp
#include "Synchronizer.h"
//////////////////////////////////////////////////////////////////////
// Wait/Notify
//////////////////////////////////////////////////////////////////////

BOOL CSynchronizer::Wait(DWORD dwTimeOut)
{
    if(CSingleLock::IsLocked())
    {
        CSingleLock::Unlock();
    }
    return CSingleLock::Lock(dwTimeOut);
}

BOOL CSynchronizer::Notify()
{
    if(CSingleLock::IsLocked())
    {
        return CSingleLock::Unlock();
    }
    return TRUE;
}

The CSynchronizer class tries to gain a Lock on the syncobj when constructed (locks initially). The class also defines a conversion operator BOOL. If the CSynchronizer object has gained a lock, the operator BOOL returns TRUE else it returns FALSE (that is the object is not at all constructed, if the lock is not gained initially). It also contains two functions called Wait and Notify (yes very similar to Java's wait and notify). Wait function releases the owned Lock and tries to gain it again. It returns TRUE after successful gain of Lock. It also takes care to Unlock if it has already owned a Lock. Notify function releases the owned Lock. It returns TRUE if the Lock is already released.

SYNCHRONIZED macro

Next we come to the most important thing in the code, the SYNCHRONIZED macro. Before studying about how this macro works, we shall see about the scope of objects declared in if condition. For example see below:

//we can declare a variable in the if condition as follows
if(int i = 10)
{
 //we can access variable i here if the if block is evaluated
}
else
{
 //we can access variable i here if the else block is evaluated
}
//but i cannot be accessed here

The variable declared in the if condition is valid in C++. It can be used in if block or else block. The scope of variables declared in if block goes at the end of if block. So we cannot access the variables declared in if after the if block. So we are going to create CSynchronizer object in the if block (which will eventually go out of scope at the end of if block). But to make the if allow object creation, the object has to return some boolean value (any integer). That is why our class has an operator BOOL conversion. We create an object and store it in the variable in if, which can be used throughout the if (got the idea).

For example:

if(CSynchronizer syn = CSynchronizer(&some_critical_section))
{
 //do the processing on critical section of code here inside this if
}

//the variable syn cannot be used here since it's scope is over after if
//and hence the lock gained is released automatically by destructor

The above template code shows a simple synchronized block. The CSynchronizer is created on some critical section (or some other sync object) and is stored in variable syn. The variable syn returns TRUE if the lock is gained or waits till the lock is gained on critical section. The lock is released on critical section automatically at the end of if block because the scope of variable declared in if is over!!! How is the lock released?? Because CSynchronizer is a derived class of CSingleLock whose destructor takes care of releasing the lock on destruction. Since CSynchronizer object gets unwound from stack, the lock is released by the base class CSingleLock. What about the Wait and Notify functions. For example if we have gained a lock using CSynchronizer and we want to wait for a flag to be set by another thread, we have to release the Lock temporarily and allow the other thread to set the flag after doing some critical section of code. Then we have to gain the Lock again and check if the flag is set. This will go in a loop till the flag is set by some other thread which can be coded as follows:

if(CSynchronizer syn = CSynchronizer(&some_critical_section))
{
  //do some processing
  //check if flag is set
  while(some_flag_is_not_set)
  {
    //release the lock temporarily to allow other thead to process
    //syn.Unlock();
    //gain the lock again
    //syn.Lock();
    //the above are two function calls and we may miss one call or 
    //change the order (Unlock first and Lock next) so
    //call wait
    syn.Wait(); //which is the same as Unlock(); first and Lock(); second
  }
  //the lock gained is still there so
  //do the processing
}//if (the lock is released automatically)

The above if condition is summarized in the small macro as follows:

#define SYNCHRONIZED(slock, cs) if(CSynchronizer slock = CSynchronizer(cs))

where the first parameter slock is the name of the identifier of synchronizer inside the synchronized block and cs is the address of some critical section or any other sync object.

So the same wait template code inside a synchronized can be rewritten as:

SYNCRHONIZED(syn, &some_critical_section)
{
 //do delicate code on critical section or data
 while(some_flag_is_not_set)
 {
  //wait till the flag is set
  syn.Wait();
 }//while 
 //do delicate processing
 //call notify if you require explicit Unlocking
}//SYNCHRONIZED

The use of Notify is not needed since all threads are notified automatically when synchronizer goes out of scope after if block. But to make feel a complete code, I have just added Notify. To use the SYNCHRONIZED in your MFC applications copy the Synchronizer.h and Synchronizer.cpp code into your MFC application as class CSynchronizer and use it by including Synchronizer.h.

Enjoy easy synchronization

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

Share

About the Author

rsankaran
Architect
India India
No Biography provided

Comments and Discussions

 
QuestionCMutex if thread started with _beginthreadx function Pinmembereryreyeryeryeryeyryye21-Mar-07 0:48 
QuestionThreads PinmemberKabini21-Jul-06 23:45 
GeneralAvoid copy and else problems PinmemberMrBern20-Apr-06 8:26 
GeneralRe: Avoid copy and else problems Pinmemberrsankaran24-Apr-06 7:58 
QuestionWhere is the Code? PinmemberDaniel Kamisnki14-Feb-04 4:02 
QuestionCould you fix the horizontal scrolling annoyance? PinmemberWREY7-Jul-03 21:53 
AnswerRe: Could you fix the horizontal scrolling annoyance? Pinmembergzlozhzl7-Jul-03 22:47 

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.

| Advertise | Privacy | Mobile
Web01 | 2.8.141029.1 | Last Updated 8 Jul 2003
Article Copyright 2003 by rsankaran
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid