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

Automatic thread synchronization - Exception based class library

By , 18 Mar 2013
 

Introduction

Exceptions are always preferred to return codes in the object oriented world. As we all know it will make the normal flow a fall through and separates the alternate flows (exceptional). It not only makes the normal execution flows efficient (as there is no need to check for returns) but also makes the code more clean and easy to understand. You may be thinking why I am chatty about the very well known facts. Well, I am coming to the point. How about making the automatic thread synchronization classes throw exceptions when the thread wait fails or times out? This is the topic for our discussion.  

Background

This is how we normally deal with thread synchronization, see the code below:

HANDLE hMutex = ::CreateMutex(…);
// other code
DWORD dwReturn = WaitForSingleObject(hMutex,dwTimeOut);
switch(dwReturn)
{
    case WAIT_OBJECT_0:
    // handle success case
    case WAIT_FAILED:
    // handle wait failed.
    case WAIT_TIMEOUT:
    // handle timeout
    case WAIT_ABANDONED:
    // handle wait abandoned
}

This is OK in the C world. But it looks bad in C++ code. We have MFC synchronization classes such as CMutex, CEvent, CSemaphore etc., that wrap the APIs. Also the associated classes such as CSinlgeLock and CMultiLock make the locks acquire and release automatically. But the Lock function returns either TRUE or FALSE when the wait succeeds or fails. In the case of failure the exact return code is not available. 

See the code snippet below which uses CSingleLock

CSingleLock MyLock(&m_myMutex);

if (MyLock.Lock(500))   
{
    // Lock success
    MyLock.Unlock();
} 

Now, I think we are in the context and I am proposing a solution to get rid of error codes and use exceptions instead. The synchronization classes which I offer throw exceptions when a wait failed case happens.

This will make the code cleaner as we can deal each of error cases such as WAIT_FAILED, WAIT_TIMEOUT etc., in separate exception handling blocks. The following table shows the error codes and their corresponding exceptions:

Error code

Exception

WAIT_OBJECT_0

Normal case

WAIT_FAILED

SyncObjectWaitFailed 

WAIT_TIMEOUT

SyncObjectWaitTimeOut

WAIT_ABANDONED

SyncObjectWaitAbandoned

WAIT_ABANDONED _0 +

SyncObjectWaitAbandoned

The following is the army of classes I have coded to achieve thread synchronization using an exception based strategy:

  • SynchronizerBase
  • CriticalSectionSynchronizer
  • EventSynchronizer 
  • MutexSynchronizer 
  • SemaphoreSynchronizer
  • SingleObjectLock
  • MultiObjectLock

The Synchronizer group of classes provides facilities for thread synchronization using CriticalSection, Event, Mutex, and Semaphore. These classes can be used with automatic locking and lock releasing objects such as SingleObjectLock and MultiObjectLock. SynchronizerBase is the base class for this group of classes. These classes throw exceptions when conditions such as wait timeout arise. As we all know, an exception based mechanism avoids error code checking in the success case. It separates the normal flow and exceptional flow.  

  • CriticalSectionSynchronizer - This class wraps the critical section. It serves as a facilitator for the automatic lock handling class, the SingleObjectLock. SingleObjectLock's constructor calls Lock and destructor calls UnLock.
  • MutexSynchronizer - This class wraps the mutex kernel object. It throws SyncObjectCreationFailed, SyncObjectWaitFailed, SyncObjectWaitTimedOut, SyncObjectWaitAbandoned  exceptions depending on the mutex creation or return of the WaitForSingleObject function.  It acts a facilitator for the automatic lock acquire and release by the SingleObjectLock and   MultiObjectLock classes.
  • SemaphoreSynchronizer - This class wraps the semaphore kernel object. It throws the SyncObjectCreationFailed, SyncObjectWaitFailed, SyncObjectWaitTimedOut exceptions depending on the semaphore creation or return of the WaitForSingleObject function. It acts as a facilitator for automatic lock acquire and release by the SingleObjectLock and MultiObjectLock classes.
  • SingleObjectLock - This class handles automatic lock acquire and release by using a constructor and destructor. It ensures that the lock is properly released with the help of an UnLock call in the destructor. It works with the single object wait function, WaitForSingleObject.
  • MultiObjectLock - This class handles the automatic lock acquire and release by using a constructor and destructor. It works with multiple object wait functions such as the WaitForMultipleObjects and MsgWaitForMultipleObjects. It throws SyncObjectWaitTimedOut,  SyncObjectWaitFailed, SyncObjectWaitAbandoned depending on the return value of the wait function. This object can be used to wait in a worker thread as well as a UI thread. In case a UI thread is specified, it will use MsgWaitForMultipleObjects internally.  The LockHolder keeps the synchronization objects. The sync objects should be added to the LockHolder and passed to the MultiObjectLock instance. The LockHolder can be reused with the local instances of MultiObjectLock.
  • LockHolder - Holds the list of synchronization objects. It is used with MultiObjectLock. The synchronizer list is separated because we can create local instances of MultiObjectLock while the LockHolder object can be reused. So no need to add the synchronizer objects again while creating another instance of MultiObjectLock.

Using the code 

Now, how to use these classes. For an example, see the code below. It shows how you can use the SingleObjectLock. The SingleObjectLock uses WaitForSingleObject whereas MultiObjectLock uses WaitForMultipleObjects and MsgWaitForMultipleObjects (if UI flag is ON). 

// It simulates the mutex wait timedout condition with the help
// of a child thread.
void CThreadSyncClassesDemoDlg::OnBnClickedButtonMutexTimeout()
{
    try
    {
        THREAD_INFO* pstThreadInfo = new THREAD_INFO;
        pstThreadInfo->eObjType = MUTEX;
        pstThreadInfo->csObjName = GetUniqueKernelObjName();

        // Let thread acquire the mutex
        CWinThread* pTimeoutThread = AfxBeginThread(ExceptionSimulatorThread,pstThreadInfo,0,0);
        AfxMessageBox(_T("Thread acquired lock...click OK to timeout"));
        
        // Try to acquire the mutex and get timeout
        MutexSynchronizer MtxSync(0,FALSE,pstThreadInfo->csObjName);
        SingleObjectLock Lock(&MtxSync,100);
    }
    catch (SyncObjectWaitTimeOut& WaitTimeOut)
    {
        // Your wait timeout handler
    }
    catch(SyncObjectWaitFailed& WaitFailed)
    {
        // Your wait failed handler
    }
    catch(SyncObjectWaitAbandoned& WaitAbandoned)
    {
        // Your wait abandoned handler
    }
    catch(GeneralException& GenException)
    {
        // General handler
    }
} 

The MultiObjectLock needs a helper class LockHolder which manages a container of synchronization objects. As the purpose of MultiObjectLock is to operate as a stack variable, the LockHolder object can be reused. This is particularly handy in the case of waiting in a UI thread where we have to loop around while pumping the messages. The call to MsgWaitForMultipleObjects is wrapped inside a MultiObjectLock constructor. It releases the objects that are signaled in the destructor. The HasMessageInQueue() function can be used to see if MsgWaitForMultipleObjects returned due to a message in the queue. If it returns false that means an object is signaled and we can quit the loop.   

Here is how you can use MultiObjectLock:

// This will simulate the WaitForMultipleObjects timeout condition
void CThreadSyncClassesDemoDlg::OnBnClickedButtonMultiTimeout()
{
    try
    {
        CString csMutexName = GetUniqueKernelObjName();
        
        THREAD_INFO* pstThreadInfo = new THREAD_INFO;
        pstThreadInfo->eObjType = MUTEX;
        pstThreadInfo->csObjName = csMutexName;

        // Let the worker thread acquire the mutex...
        CWinThread* pTimeoutThread = AfxBeginThread(ExceptionSimulatorThread,pstThreadInfo,0,0);
        AfxMessageBox(_T("Let the other thread acquire the mutex and we time out...now close"));

        // Prepare mutex and semaphore objects
        typedef std::auto_ptr<SynchronizerBase> SyncObjPtr;
        SyncObjPtr pMutexPtr(new MutexSynchronizer(0,FALSE,csMutexName));
        SyncObjPtr pSemPtr(new SemaphoreSynchronizer(0,FALSE,1,1,1,0,_T("TEST_SEMAPHORE")));
        
        // Add mutex and semaphore
        LockHolder lhLockHolderObj;
        lhLockHolderObj.AddSynchronizer(pMutexPtr.get());
        lhLockHolderObj.AddSynchronizer(pSemPtr.get());
        
        // Now, try for a WaitForMultipleObjects.
        // It will generate WaitTimeout exception.
        MultiObjectLock Lock(&lhLockHolderObj,TRUE,100,FALSE,0); 
    }
    catch (SyncObjectWaitTimeOut& WaitTimeOut)
    {
        AfxMessageBox(WaitTimeOut.GetDescription());
    }
    catch(SyncObjectWaitFailed& WaitFailed)
    {
        AfxMessageBox(WaitFailed.GetDescription());
    }
    catch(SyncObjectWaitAbandoned& WaitAbandoned)
    {
        AfxMessageBox(WaitAbandoned.GetDescription());
    }
    catch(GeneralException& GenException)
    {
        AfxMessageBox(GenException.GetDescription());
    }
} 

See below on how to use MultiObjectLock from a UI thread:

// It will simulate the MsgWaitForMultipleObjects
void CThreadSyncClassesDemoDlg::OnBnClickedButtonUiMultiWait()
{
    m_ProgressDemo.SetRange(0,PROGRESS_MAX);
    
    EventSynchronizer EventSync(0,FALSE,TRUE,FALSE,0);
    typedef std::auto_ptr<SynchronizerBase> SyncObjPtr;
    SyncObjPtr pEventPtr(new EventSynchronizer(0,FALSE,TRUE,FALSE,0));
    
    CWinThread* pUIWaitThread = AfxBeginThread(UIWaitDemoThread,pEventPtr.get(),0,0);
    
    LockHolder lhLockHolderObj;
    lhLockHolderObj.AddSynchronizer(pEventPtr.get());
    for(;;)
    {
        // Call MsgWaitForMultipleObjects and allow to pass input events.
        MultiObjectLock Lock(&lhLockHolderObj,FALSE,INFINITE,TRUE,QS_ALLINPUT); 
        // If the reason to return is not a message, it is the case when the event is
        // signalled, then break the loop.
        // Ensure the messages are dispatched.
        AfxGetApp()->PumpMessage();
        if( !Lock.HasMessageInQueue() )
        {
            AfxMessageBox(_T("Wait complete"));
            break;
        }
    }
}  

WrapUp

I have prepared a demo application which simulates the error cases. This will help in quickly understanding the intended usage. MultiObjectLock supports up to MAXIMUM_WAIT_OBJECTS. You have to use the technique of creating multiple threads and wait on thread handles if you want to support more than that. I hope these classes are useful and are more aligned with the object oriented way of dealing with thread synchronization. I am eagerly waiting to see how it is being used. Thank you for the patient reading.

History

  • 10-Feb-2013 - Released as a set of classes.
  • 18-Mar-2013 - Released as a DLL and made some minor fixes. The demo application uses this library.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sudheesh.P.S
Architect
India India
Member
Working in an MNC at Trivandrum. I can be reached at sudheesh_perumbilli@yahoo.com.
My personal page http://mytechcraze.wordpress.com/

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.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionWait function resultsmvpgeoyar11 Mar '13 - 8:44 
i think that the switch statement on result of the any wait function looks as good in C++ as it looks in C.
I also do not think that using of MFC synchronization classes is such a good idea: the C++ 11 and Windows SDK have enough means to work with threads. It is like the MFC templates like CMap class: who needs it?
So IMHO it is better to work with C++/Window SDK, and use exceptions where they belong: to handle crashes.
Retuns from Wait on timeout and others are not crashes.
I have nothing against your article, just I would not recommend to use it.
geoyar

AnswerRe: Wait function resultsmemberSudheesh.P.S12 Mar '13 - 5:12 
Hi Geoyar,
 
You think that exceptions are applicable only in crash scenarios? If we look at platforms such as Java or .net, we can see that the alternate cases are treated as exceptions. For example, FileNotFoundException, InterruptedException etc. In the case of C++, as it is possible to write C code, we opt to use the Win32 APIs directly and handles the return code. The handling of error codes makes the code ugly and makes the error handling a mess. Also we need to check the success case explicitly. But in the case of an exception based strategy, the normal case is a fall through and makes code better understandable. Anyway, it is a matter of choice.
 
Thanks and regards
Sudheesh
miles to go before I sleep

GeneralRe: Wait function resultsmvpgeoyar12 Mar '13 - 7:32 
Firs, thank for reply.
Second, I am a C++ guy, so I do not know about Java. I am using C++ exceptions a lot. It is not a free lunch: you are paying for them with the execution time. Also, you are not always need to handle all possible error codes; mostly it is enough to know the result is not a success. Of course you may use exception in any place you think it fit, but as you rightly mention, it is a matter of choice.
geoyar

QuestionI have some doubt.memberyafan10 Mar '13 - 6:17 
Hi Sudheesh, thanks for sharing your idea and expressing it in a set of classes. They look pretty good, however, I have a few points/questions to make.
 
1) This is nit-picking, but I think that the class name MutexSynchronizer is a little redundant since a mutex IS a synchronizing construct by definition.
 
Just CMutex or something would be better.
 
2) If you run the demo application a number of times you will see that the ExceptionSimulatorThreads start accumulating in memory - they are never released because they are sitting on a blocked ::Sleep call. To cleanup these accumulated threads the app needs to be a bit more sophisticated (Perhaps use an event). I think you should cleanup the threads than just leaving them in memory. This would be closer to a real world usage pattern.
 
3)
You only have one lock in the demo UI thread after the simulation thread is spawned. But what if you have several threads waiting to lock the mutex and they all issue a throw due to the expected error condition from the lock call. Because of the time slicing nature of multi-threaded apps, this would be a bad situation. You potentially run the risk of having multiple throws in flight - which could be very bad.
 
There is no guarantee that a throw issued from one thread won't be preempted by another thread's throw (i.e. two throws in flight). I'm not really sure what happens in that scenario.
 
A better test would be to have two set of threads spawned: the simulation threads, and locking threads that want access to the mutex.
 

Thanks,
 
-y
AnswerRe: I have some doubt. [modified]memberSudheesh.P.S11 Mar '13 - 5:30 
Hi Yafan,
 
Thank you for your creative criticism.
Please see my answer on your queries.
 
#1)Regarding the object names.
CMutex can be a very generic name and frameworks like MFC already uses such name.
So I wan't to introduce my own names. In fact, I changed the names multiple times while preparing the code. I felt that the names should be something unique.
 
#2)Regarding the cleanup of threads.
This is just a simulation of error cases. I have not cared the threads as the demo application it is not supposed to run for a long time. If you look at the code you can find that the prohibited APIs like ::TerminateThread used. We all know that it should not be used in any case as it can cause resource leaks . But I have used it to simulate the situations such as WAIT_ABANDONED for a mutex. I just want to show the concept and make the demo simple.
 
#3)Regarding multiple threads getting the same error codes (exceptions).
If that is the case it should happen even if such exception mechanism is not there. My exception mechanism just maps the error codes to corresponding exception objects. And I would like to add one more point. If multiple threads are waiting for a kernel object and timeout happens, WAIT_TIMEOUT or other cases such as WAIT_ABONDONED (in case of mutex) happens, and the waiting thread wants to handle such error cases, the exceptions can be treated in the same way. No need to think to pre-emption, as the wait function returns, the thread is scheduled for execution. It is just as any code that is run by a scheduled thread. There is nothing special for exceptions. So I don't think there is anything to worry in such cases. This is my understanding.
 
Thanks and regards
Sudheesh
miles to go before I sleep


modified 11 Mar '13 - 11:54.

GeneralRe: I have some doubt.memberyafan11 Mar '13 - 8:51 
Hi Sudhesh, thanks for your replies. I can sort of agree with you on 1 and 2 since they are subjective. However, I still have some doubt with your response to point 3.
 
The point I am making is can be phrased with the question: "Are C++ exceptions (i.e. throw/catch) thread safe".
 
In other words: if I have a thread that throws an exception because it is using your lock mechanism, and that sometime during the processing of that throw, another thread that is also trying to perform a lock to gain access to the same resource, again using your lock mechanism, and that thread also throws an exception, what happens to the throw from the first thread? There could now be 2 throws in flight. Which one is caught?
 
Are you absolutely sure that there are no issues with exceptions and thread safety? You seem to be saying that there isn't because there is no difference between your exception based framework and using return codes. This maybe true, but I'm questioning that assertion.
 
I'm just not sure that a user mode thread can prevent preemption even when it is processing a throw/catch. I couldn't find anything definitive on the subject, so I am not entirely sure.
 
From Googeling around, I did see that an uncaught exception can crash not only the thread but the entire process.
 
Also, I'm not sure this link is relevant to the question, but it does seem to imply that if you want catch an exception from multiple threads, from a primary thread, you need to transport the exception using the mechanism explained in this link:
 
http://msdn.microsoft.com/en-us/library/dd293602.aspx[^]
 

What do you think?
 
-y
GeneralRe: I have some doubt.memberSudheesh.P.S12 Mar '13 - 4:58 
Hi Yafan,
 
Thank you for taking the discussion to a new arena, "Transporting exceptions across threads".
This is altogether a new input to me. I guess such a mechanism is used in PPL (Parallel Patterns Library) introduced with VS2010. Where there are concurrent tasks possible that can run on different cores. The constructs such as parallel_foreach that can concurrently execute the work items in the loop. Now, I guess the exception transportation mechanism makes it possible to handle the exceptions thrown from concurrent tasks at once place. I am not sure about the internals of this. This is my wild wild guess!. The transportation of exception is altogether a new concept. As per MSDN I could see that is available from VS2010. Before that there was no support to catch the exception thrown in a child thread from a parent thread. Also this kind of mechanism needs some additional code to transport exceptions. In my case, I have not even dream of it while designing my auto classes. I could assure you that my classes are designed to be run with in a thread. These exceptions are not transportable across threads. It will work like any other exception objects created in a threads stack. Hope the intention is clear. Once again, thank you for introducing me an entirely new concept of exception transportation across threads.
 
Thanks and regards
Sudheesh
miles to go before I sleep

QuestionPerformance Considerationsmemberjcurl5 Mar '13 - 2:33 
I think you'll find that the .NET BCF doesn't use exceptions here as it can be considered regular and often that timeouts occur. Many programmers set timeouts for small periods, such as 100ms, in an event loop. Exceptions are processor intensive - thus only use them if you think they should generally never occur.
AnswerRe: Performance ConsiderationsmemberSudheesh.P.S5 Mar '13 - 5:19 
I would like to quote the statements from C++ FAQ by legendary Stroustrup himself.
Please see http://www.stroustrup.com/bs_faq2.html#exceptions-why[^]
 
"but exceptions are expensive!: Not really. Modern C++ implementations reduce the overhead of using exceptions to a few percent (say, 3%) and that's compared to no error handling. Writing code with error-return codes and tests is not free either. As a rule of thumb, exception handling is extremely cheap when you don't throw an exception. It costs nothing on some implementations. All the cost is incurred when you throw an exception: that is, "normal code" is faster than code using error-return codes and tests. You incur cost only when you have an error."
 
Now, coming to the point on looping with small timeouts on events.
I don't get you clearly, is that a good way to deal with events?
miles to go before I sleep

GeneralRe: Performance Considerationsmemberjcurl5 Mar '13 - 6:07 
Hi Sudheesh,
 
I've seen often that code waits for an event, and if it doesn't occur, they timeout after, say, 100ms, followed by repetition of the loop. You're question - is this a good way to deal with events? clearly no. In some cases it's too much work to modify existing code from a polling mechanism to an event based system. But an event based system would probably have less need for exceptions on timeout.
 
In any case, I think your idea is viable if it isn't part of a main loop where the exception could occur regularly, but is a one-off (e.g. initial connection).
 
Thanks for an interesting article.
Jason.
GeneralRe: Performance ConsiderationsmemberSudheesh.P.S6 Mar '13 - 4:03 
Dear Jason,
 
Thank you Jason.
I understand your point. In case of situations were frequent timeouts are expected this may not be good.
But in normal cases we can adopt exception mechanism. I cannot find such a mechanism in frameworks such as MFC. Even the new C++11 standard does not use this mechanism. I could understand that C++ 11 suggests classes such as std::mutex. But it is based on return codes I think. This is altogether a new idea. I expect comments from the community on this. Thank you for sharing your comment.
 
Thanks and regards
Sudheesh
miles to go before I sleep

QuestionOpening sentence - I'd question the wisdom of using exceptions to replace return codes.memberMike Diack5 Mar '13 - 1:27 
It's not directly related, but I'd take issue with your opening sentence:
 
"Exceptions are always preferred to return codes in the object oriented world"
 
Exceptions, I've always been taught, are for handling exceptional conditions (e.g. out of memory/resources), NOT for expected and reasonable cases, e.g. opening a file (that may/may not be present).
 
Using them instead of return codes is likely to lead to a mess in my opinion.
AnswerRe: Opening sentence - I'd question the wisdom of using exceptions to replace return codes.memberSudheesh.P.S5 Mar '13 - 5:05 
Sorry if the first sentence has confused you.
Hope you have read the next two sentences too.
 
"Exceptions are always preferred to return codes in the object oriented world. As we all know it will make the normal flow a fall through and separates the alternate flows (exceptional). It not only makes the normal execution flows efficient (as there is no need to check for returns) but also makes the code more clean and easy to understand"
 
What I meant is that it is beneficial in error/exceptional cases (error case return codes) and not in normal cases.
miles to go before I sleep

GeneralInteresting approachmemberH.Brydon9 Feb '13 - 15:57 
Interesting approach to this problem. Thanks for developing. +5
--
Harvey

GeneralRe: Interesting approachmemberSudheesh.P.S11 Feb '13 - 3:02 
Thanks
miles to go before I sleep

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 18 Mar 2013
Article Copyright 2013 by Sudheesh.P.S
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid