Click here to Skip to main content
Licence Public Domain
First Posted 14 Aug 2007
Views 16,797
Downloads 59
Bookmarked 6 times

Java-like Synchronized Blocks in C++ with Pure WinAPI

By | 14 Aug 2007 | Article
An article on how to make C++ synchronized multi-threaded code in Win32 nice, clear and more bugless

Introduction

Working with synchronized blocks and data members using Win32 can be a pretty annoying thing. It not only causes bulky code -- especially comparing with languages like Java -- but it also provokes the deadlock bugs. Let's see how we can make our lives simpler in pure C++ without involving additional libraries.

Problem

When you are writing the C++ in pure WinAPI and you have to deal with threads, you'll probably use the synchronization based on CRITICAL_SECTION structure and the family of functions XXXCriticalSection(). Such code usually looks pretty bad even for simple synchronized accessor methods, not to mention dealing with multiple return points in synchronized methods and throwing exceptions within such methods. Don't forget that critical sections should probably be initialized and destroyed. Most likely, you should write something like following:

class A
{
    mutable CRITICAL_SECTION m_sync;
    int m_some_protected_int_val;
    ...

    A()
    { 
        InitializeCriticalSection(&m_sync); 
    }

    ...
    ~A()
    { 
        DeleteCriticalSection(&m_sync); 
    }

    ...

    //does something with m_some_protected_int_val from another thread
    void do_job()
    {
        EnterCriticalSection(&m_sync);
        if(/* some condition */)
        {
            m_some_protected_int_val++;
            LeaveCriticalSection(&m_sync);
            return;
        }
        else
        {
            /* some other things */
            /* and what if we throw exception ? */
            /* without this catch - prepare for deadlock! */
            try
            {
                throw "exception";
            }
            catch(char*)
            {
                LeaveCriticalSection(&m_sync);
                throw;
            }
        }
        
        /* and more other things */
        
        LeaveCriticalSection(&m_sync);
    }


    int get() const
    {
        EnterCriticalSection(&m_sync);
        int ret = m_some_protected_int_val;
        LeaveCriticalSection(&m_sync);
        return ret;
    }
};

Do you agree that it is ugly and unforgivably cumbersome? It makes you think about the operator goto, doesn't it? Your mom will be angry!

Solution

It would be really nice if we managed to work with synchronized methods and blocks with the elegance of the languages that were born for it, like Java and C#, and indeed we can! The first step is pretty straightforward. Let's wrap the CRITICAL_SECTION structure into simple a class named critical_section_t that will transparently initialize it in the constructor and delete in the destructor. Now we can write something like this:

class A
{
    critical_section_t m_sync;
    int m_some_protected_int_val;
    ....

    /* ctor and dtor are not interesting anymore */ 

    ...

    //does something with m_some_protected_int_val from another thread
    void do_job()
    {
        m_sync.enter();
        if(/* some condition */)
        {
            m_some_protected_int_val++;
            m_sync.leave();
            return;
        }
        else
        {
            /* some other things */
            /* exceptions still are pain */
            try
            {
                throw "exception";
            }
            catch(char*)
            {
                m_sync.leave();
                throw;
            }
        }
        
        /* and more other things */
        
        m_sync.leave();
    }


    int get() const
    {
        m_sync.enter();
        int ret = m_some_protected_int_val;
        m_sync.leave();
        return ret;
    }
};

Well, we've gotten rid of initialization and clean-up, but where is the promised Java-like beauty? Patience, it is just one more slight dash to go. Let's define another class named synchronized_block, whose objects will call enter() for us on creation and leave() on destruction. Look how nice it can be now:

class A
{
    critical_section_t m_sync;
    int m_some_protected_int_val;
    ...

    //does something with m_some_protected_int_val from another thread
    void do_job()
    {
        synchronized_block lock(m_sync);
        if(/* some condition */)
        {
            m_some_protected_int_val++;
            return;
        }
        else
        {
            /* some other things */
            /* and even ot will be OK to throw exception */
            /* and do not worry about it */
            throw "exception";
        }
        
        /* and more other things */
    }

    int get() const
    {
        synchronized_block lock(m_sync);
        return m_some_protected_int_val;
    }

};

From now on, you don't have to be bored by initializing and deleting critical sections and, what is even more important, you don't have to remember to call LeaveCriticalSection() on each return point or block ending. That will finally give you much less doubtful pleasure when dealing with deadlocks. What about throwing exceptions now? Magically, this issue is solved too! Destructors are always called during stack unwinding, so you can forget about the problem. Last but not least: your code looks lots nicer and more clear. Of course, you have already noticed that you can make synchronized blocks exactly like synchronized methods. The method is also a block, isn't it?

Obviously, you can still use the m_sync object itself. For example, if you need to use the TryEnterCriticalSection() API for Windows 2000 and greater only, just call the try_enter() method and leave() afterwards. In addition, the critical_section_t class has a casting operator that allows you to get the address of the underlying structure. Just use (LPCRITICAL_SECTION)m_sync if you really-really need to.

You don't have to worry about performance because both classes are declared fully inline and all of the allocations are done on the stack, so we are still very efficient. No macros, by the way. Did they teach you that macros are evil? My final remark goes to those who are confused by the last get() method. The point is, the destructor of lock is called after the copy constructor of int, so it is perfectly safe. You can freely use the attached code and write me your opinions and questions.

Love C++!

The Files

The attached project was created with MS Visual Studio 2005.

History

  • 8 August, 2007 - first version

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication

About the Author

Michael Patlis

Web Developer

Israel Israel

Member

I am 29 years old software developer from Yokneam, Israel. I am working with C++/Win32/MFC/WinCE/PocketPC for Arad Technologies Ltd. Additionally I am in the middle of my studies for master's degree in computer science in the Academic College of Tel-Aviv - Yaffo.

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
GeneralIt's been done Pinmemberstdedd8:15 14 Aug '07  
GeneralRe: It's been done PinmemberMichael Patlis9:05 14 Aug '07  
GeneralRe: It's been done Pinmemberstdedd9:21 14 Aug '07  
GeneralRe: It's been done PinmemberMichael Patlis9:45 14 Aug '07  
GeneralRe: It's been done Pinmembervoidbent1:03 15 Aug '07  
GeneralQuick question PinmemberJim Crafton6:11 14 Aug '07  
GeneralRe: Quick question PinmemberMichael Patlis7:14 14 Aug '07  

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
Web03 | 2.5.120517.1 | Last Updated 14 Aug 2007
Article Copyright 2007 by Michael Patlis
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid