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(…);
DWORD dwReturn = WaitForSingleObject(hMutex,dwTimeOut);
switch(dwReturn)
{
case WAIT_OBJECT_0:
case WAIT_FAILED:
case WAIT_TIMEOUT:
case 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))
{
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:
SynchronizerBaseCriticalSectionSynchronizer-
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).
void CThreadSyncClassesDemoDlg::OnBnClickedButtonMutexTimeout()
{
try
{
THREAD_INFO* pstThreadInfo = new THREAD_INFO;
pstThreadInfo->eObjType = MUTEX;
pstThreadInfo->csObjName = GetUniqueKernelObjName();
CWinThread* pTimeoutThread = AfxBeginThread(ExceptionSimulatorThread,pstThreadInfo,0,0);
AfxMessageBox(_T("Thread acquired lock...click OK to timeout"));
MutexSynchronizer MtxSync(0,FALSE,pstThreadInfo->csObjName);
SingleObjectLock Lock(&MtxSync,100);
}
catch (SyncObjectWaitTimeOut& WaitTimeOut)
{
}
catch(SyncObjectWaitFailed& WaitFailed)
{
}
catch(SyncObjectWaitAbandoned& WaitAbandoned)
{
}
catch(GeneralException& GenException)
{
}
} 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:
void CThreadSyncClassesDemoDlg::OnBnClickedButtonMultiTimeout()
{
try
{
CString csMutexName = GetUniqueKernelObjName();
THREAD_INFO* pstThreadInfo = new THREAD_INFO;
pstThreadInfo->eObjType = MUTEX;
pstThreadInfo->csObjName = csMutexName;
CWinThread* pTimeoutThread = AfxBeginThread(ExceptionSimulatorThread,pstThreadInfo,0,0);
AfxMessageBox(_T("Let the other thread acquire the mutex and we time out...now close"));
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")));
LockHolder lhLockHolderObj;
lhLockHolderObj.AddSynchronizer(pMutexPtr.get());
lhLockHolderObj.AddSynchronizer(pSemPtr.get());
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:
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(;;)
{
MultiObjectLock Lock(&lhLockHolderObj,FALSE,INFINITE,TRUE,QS_ALLINPUT);
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.
Working in an MNC at Trivandrum. I can be reached at sudheesh_perumbilli@yahoo.com.
My personal page http://mytechcraze.wordpress.com/