Click here to Skip to main content
Email Password   helpLost your password?

Introduction

This article shows how to code a synchronized statement in C++ that works in a similar way to Java. The target of this code is to make a piece of code like the following, compilable and executable in C++:

synchronized(myMutex)
{
    //TODO put synchronized code here

}

The Mutex class

The following piece of code presents a mutex class with lock/unlock semantics (common in many libraries):

//mutex class

class Mutex
{
public:
    //the default constructor

    Mutex()
    {
        InitializeCriticalSection(&m_criticalSection);
    }

    //destructor

    ~Mutex()
    {
        DeleteCriticalSection(&m_criticalSection);
    }

    //lock

    void lock()
    {
        EnterCriticalSection(&m_criticalSection);
    }

    //unlock

    void unlock()
    {
        LeaveCriticalSection(&m_criticalSection);
    }

private:
    CRITICAL_SECTION m_criticalSection;
};

There is nothing particularly special for the above class:

We are going to use critical sections, but any synchronization primitive applies.

The Lock class

In order to be consistent with the C++ established code practices, we need a special class for implementing the RAII (Resource Acquisition Is Initialisation) pattern. The following piece of code shows such a class:

//synchronization controller object

class Lock
{
public:
    //the default constructor

    Lock(Mutex &mutex) : m_mutex(mutex), m_locked(true)
    {
        mutex.lock();
    }

    //the destructor

    ~Lock()
    {
        m_mutex.unlock();
    }

    //report the state of locking when used as a boolean

    operator bool () const
    {
        return m_locked;
    }

    //unlock

    void setUnlock()
    {
        m_locked = false;        
    }

private:
    Mutex &m_mutex;
    bool m_locked;
};

Things to note for this class:

Using the above class is pretty straightforward:

Mutex mutex1;
...
Lock lock1(mutex1);
//synchronized code here

The "synchronized" macro

The synchronized statement can be coded as a macro like this:

#define synchronized(M)  for(Lock M##_lock = M; M##_lock; M##_lock.setUnlock())

where, the parameter M is the mutex variable to use for locking.

Example of using the "synchronized" macro

The following piece of code shows how to use the synchronized macro: it coordinates two threads that print the alphabet in the standard output. Without synchronization, the output is not correct:

//thread count

int thread_count = 0;

//mutex

Mutex mutex1;

//example thread

DWORD CALLBACK thread_proc(LPVOID params)
{
    for(int i = 0; i < 10; ++i)
    {
        synchronized(mutex1)
        {
            for(char c = 'A'; c <= 'Z'; ++c)
            {
                cout << c;
            }
            cout << endl;
        }
    }
    thread_count--;
    return 0;
}

//main

int main()
{
    thread_count = 2;
    CreateThread(0, 0, thread_proc, 0, 0, 0);
    CreateThread(0, 0, thread_proc, 0, 0, 0);
    while (thread_count) Sleep(0);
    getchar();
    return 0;
}

How it works

The macro exploits the nature of the for statement of C++ to do the following (in the presented order):

  1. initialization part: a local lock variable is defined that locks the given mutex; the lock variable contains an internal flag which is set to true.
  2. test part: the lock variable is tested and found to be true: the code inside the loop is executed.
  3. increment part: the lock variable's internal flag is set to false.
  4. test part: the lock variable is tested and found to be false: the loop exits.
  5. exit part: the lock variable is destroyed, unlocking the mutex.

Advantages over classic RAII

Using this way to code RAII has some advantages over the traditional method:

Notes

The synchronized macro is exception-safe, since it unlocks its mutex upon destruction.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralHere is my version.
pedro.alves
9:02 20 Dec '05  
I had made this too some time ago.
My version is easily extendable. Here it goes:

syncronized.h -----------------------

#ifndef SYNCHRONIZED_H
#define SYNCHRONIZED_H

/*
make a RAII wrapper class to your synch object, and
define a make_synchronizer(T& m) method.

example for Qt's QMutex:

#include "syncronized.h"

struct synch_QMutex : public Synchronizer
{
synch_QMutex(::QMutex& m) : m_(&m)
{
m_.lock();
}

~synch_QMutex()
{
m_.unlock();
}

::QMutex m_;
};

inline synch_QMutex make_synchronizer(::QMutex& m)
{
return synch_QMutex(m);
}

...

and then:

void test()
{
   QMutex mutex;
   synchronized(mutex)
   {
   ...
   }
}
*/

namespace synchronizer {

struct Synchronizer
{
   Synchronizer() : lock(0) {}
   mutable int lock;
};

}

// uses ScopeGuard trick.
// const reference to temp has life extended.
#define synchronized(X) \
   for (synchronizer::Synchronizer const &__lockable__ \
               = synchronizer::make_synchronizer(X); \
         !__lockable__.lock; \
         __lockable__.lock++)

#endif


-----------------------

What do you think?

Pedro Alves


GeneralRe: Here is my version.
pedroalves
0:18 26 Dec '05  
Blah, bug/typo in the comments!

replace:
::QMutex m_;

with:
::QMutex& m_;

around line 24 in file syncronized.h

Cheers,
Pedro Alves



GeneralRe: Here is my version.
pedroalves
0:21 26 Dec '05  
Replace:
#define synchronized(X) \
   for (synchronizer::Synchronizer const &__lockable__ \
               = synchronizer::make_synchronizer(X); \
         !__lockable__.lock; \
         __lockable__.lock++)

with:
#define synchronized(X) \
   if (0) else for (synchronizer::Synchronizer const &__lockable__ \
               = synchronizer::make_synchronizer(X); \
         !__lockable__.lock; \
         __lockable__.lock++)

for VS6 compatibily (untested)

Cheers,
Pedro Alves

GeneralC++ is not Java ... It's better
[Deep]Xor
22:18 19 Dec '05  
There is absolutely no reason to port Java syntax to C++. Adding expressions like synchronized() or using() you only mix the languages and complicate the code.

Java & C# lack of scoped object idiom that's why they need those expressions. C++ don’t!
{
Lock lock1(mutex1);
//synchronized code here
}
It’s better, more expected, and more understandable that
synchronized(mutex1)
{ }

GeneralNeeds Improvement
David Patrick
1:02 19 Dec '05  

1. The "Mutex" class should be "CriticalSection".

Since there is actually a Mutex in win32 and it is signficantly different from the Critical Section class in terms of implementation, functionality and performance characteristics it is very misleading to call the class Mutex when in fact it does NOT use a Mutex. It's a minor issue, but none-the-less may cause confusion for future maintance programmers.

2. Shouldnt the Lock::setUnlock() function actually unlock the mutex ?

Since all it does is set the boolean flag, in effect the scope of the synchronization lasts for the life of the Lock object created for the for loop .. the life span of this object is dependent upon compiler behavior and may extend beyond the scope of the specified synchronization block itself.

3. Speaking of the boolean flag ... was it really necessary ?


I like what you are trying to do, and I think you have a pretty good "first shot" ... I'm sure after a few revisions this will be a fine alternative to the current approach of just declaring the lock object inside the curly braces for the block which is to be synchronized ( I agree, the Java approach is more readable in some circumstances )


"We need less government, not more. The idea that we can become a better society by having a bigger rule book is ridiculous, regardless of who is trying to change the rules" - Doug Goulden
GeneralRe: Needs Improvement
Achilleas Margaritis
2:22 19 Dec '05  
David Patrick wrote:
The "Mutex" class should be "CriticalSection".

Well, it is just demo code. I prefer the short name Mutex instead of the long CriticalSection, also because a critical section is a mutual-exclusion object as well.

David Patrick wrote:
Shouldnt the Lock::setUnlock() function actually unlock the mutex ?

Since all it does is set the boolean flag,

No, the function 'setUnlock' should not unlock the mutex. If it did, then it would be named 'unlock'. By using 'set' as a prefix, it is made clear that it has a different function. It could be named 'setUnlockFlag', if you like.

David Patrick wrote:
the scope of the synchronization lasts for the life of the Lock object created for the for loop

Indeed, and that's the correct design: RAII dictates a resource be released upon destruction of the Lock object.

David Patrick wrote:
the life span of this object is dependent upon compiler behavior

Microsoft compilers up to VC6 may have a problem. But as I am replying in the other post, that's Microsoft's problem, and not mine: they did not implement the VC++ 98 standard correctly. GCC has no problem.

David Patrick wrote:
I like what you are trying to do, and I think you have a pretty good "first shot"

Well, feel free to modify it as needed. It was just a short sample anyway.

GeneralRe: Needs Improvement
Roland Pibinger
12:50 19 Dec '05  
David Patrick wrote:
the life span of this object is dependent upon compiler behavior and may extend beyond the scope of the specified synchronization block itself.

Using the workaround for Microsoft compilers described here[^] the macro would look like:
#if defined (_MSC_VER)
#define synchronized(M) if(0);else for(Lock M##_lock = M; M##_lock;M##_lock.setUnlock())
#endif
I'm not sure that it works without unpleasant surprise. Unsure

Also, class Lock absolutely needs to make the copy constructor and operator= private.

GeneralC++ versions ?
Robert Bielik
23:57 18 Dec '05  
As I recall, in VC++ 6 for statements like:

for (int i = 0; i < N; ++i)
{
// Do stuff
}

for (int i = 0; i < N; ++i)
{
// Do some other stuff
}

didn't work because i would be redeclared in the second statement. Does this imply that the mutex lock variable is not released upon leaving the "synchronized" scope?

If so, this would only work in C++ .NET 2002 and higher where this flaw is remedied.

GeneralRe: C++ versions ?
Achilleas Margaritis
2:14 19 Dec '05  
Indeed, but that's VC6's problem and not mine. The C++ 98 standard says that the variables declared in a for statement are local to the for loop. It works fine for GCC.

GeneralRe: C++ versions ?
Peter Ritchie
6:35 20 Dec '05  
That's a Microsoft extension. If you turn MS extensions off the issue goes away.

PeterRitchie.com
GeneralC++ versions !
Robert Bielik
21:31 20 Dec '05  
Ah, cool. Didn't know that. Thanx!
GeneralRe: C++ versions !
pedro.alves
8:29 21 Dec '05  
You could also put this in global include file:

#define for if(0){}else for


Cheers,

Pedro Alves

GeneralRe: C++ versions !
Peter Ritchie
9:24 23 Dec '05  
Or:
#define synchronized(M) if(0){}else for(Lock M##_lock = M; M##_lock; M##_lock.setUnlock())

...since this article was posted into the MFC section; it should work with Visual Studio, out of the box.

PeterRitchie.com


Last Updated 18 Dec 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010