自动线程同步 - 基于异常的类库






3.27/5 (7投票s)
自动线程同步类,它们抛出异常而不是错误代码。
引言
在面向对象的世界中,异常总是优于返回码。众所周知,这会使正常流程变得一目了然,并区分替代流程(异常)。这不仅提高了正常执行流程的效率(无需检查返回值),还使代码更清晰易懂。您可能会想,为什么我要喋喋不休地谈论这些众所周知的事实。嗯,我正要切入正题。让自动线程同步类在线程等待失败或超时时抛出异常怎么样?这就是我们今天要讨论的主题。
背景
这就是我们通常处理线程同步的方式,请看下面的代码
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
}
这在 C 语言中是可以的。但在 C++ 代码中看起来很糟糕。我们有 MFC 同步类,如 CMutex
、CEvent
、CSemaphore
等,它们封装了 API。此外,像 CSinlgeLock
和 CMultiLock
这样的关联类会自动获取和释放锁。但是 Lock
函数在等待成功或失败时返回 TRUE
或 FALSE
。在失败的情况下,无法获得确切的返回码。
请看下面使用 CSingleLock
的代码片段。
CSingleLock MyLock(&m_myMutex);
if (MyLock.Lock(500))
{
// Lock success
MyLock.Unlock();
}
现在,我认为我们已经进入了情境,我将提出一个解决方案,以摆脱错误代码并改用异常。我提供的同步类在发生 wait
失败的情况时会抛出异常。
这将使代码更清晰,因为我们可以在单独的异常处理块中处理每个错误情况,例如 WAIT_FAILED
、WAIT_TIMEOUT
等。下表显示了错误代码及其对应的异常
错误代码 | 异常 |
WAIT_OBJECT_0 | 正常情况 |
WAIT_FAILED | SyncObjectWaitFailed |
WAIT_TIMEOUT | SyncObjectWaitTimeOut |
WAIT_ABANDONED | SyncObjectWaitAbandoned |
WAIT_ABANDONED _0 + | SyncObjectWaitAbandoned |
以下是我为实现基于异常策略的线程同步而编写的一系列类
SynchronizerBase
CriticalSectionSynchronizer
EventSynchronizer
MutexSynchronizer
SemaphoreSynchronizer
SingleObjectLock
MultiObjectLock
Synchronizer
类组提供使用 CriticalSection
、Event
、Mutex
和 Semaphore
进行线程同步的设施。这些类可以与自动锁定和解锁对象(如 SingleObjectLock
和 MultiObjectLock
)一起使用。SynchronizerBase
是此类组的基类。当出现等待超时等情况时,这些类会抛出异常。众所周知,基于异常的机制可以避免在成功的情况下进行错误代码检查。它将正常流程和异常流程分开。
CriticalSectionSynchronizer
- 此类封装了临界区。它作为自动锁定处理类SingleObjectLock
的便利。SingleObjectLock
的构造函数调用Lock
,析构函数调用UnLock
。MutexSynchronizer
- 此类封装了互斥体内核对象。它根据互斥体的创建或WaitForSingleObject
函数的返回抛出SyncObjectCreationFailed
、SyncObjectWaitFailed
、SyncObjectWaitTimedOut
、SyncObjectWaitAbandoned
异常。它充当SingleObjectLock
和MultiObjectLock
类自动锁定获取和释放的便利。SemaphoreSynchronizer
- 此类封装了信号量内核对象。它根据信号量的创建或WaitForSingleObject
函数的返回抛出SyncObjectCreationFailed
、SyncObjectWaitFailed
、SyncObjectWaitTimedOut
异常。它充当SingleObjectLock
和MultiObjectLock
类自动锁定获取和释放的便利。SingleObjectLock
- 此类通过使用构造函数和析构函数来处理自动锁定获取和释放。它通过在析构函数中调用UnLock
来确保正确释放锁。它与单个对象等待函数WaitForSingleObject
一起工作。MultiObjectLock
- 此类通过使用构造函数和析构函数来处理自动锁定获取和释放。它与多个对象等待函数一起工作,例如WaitForMultipleObjects
和MsgWaitForMultipleObjects
。它根据等待函数的返回值抛出SyncObjectWaitTimedOut
、SyncObjectWaitFailed
、SyncObjectWaitAbandoned
异常。此对象可用于在工作线程和 UI 线程中等待。如果指定了 UI 线程,它将在内部使用MsgWaitForMultipleObjects
。LockHolder
负责保留同步对象。同步对象应添加到LockHolder
并传递给MultiObjectLock
实例。LockHolder
可以与MultiObjectLock
的本地实例重复使用。LockHolder
- 持有同步对象列表。它与MultiObjectLock
一起使用。同步器列表是分开的,因为我们可以创建MultiObjectLock
的本地实例,而LockHolder
对象可以重复使用。因此,在创建另一个MultiObjectLock
实例时,无需再次添加同步器对象。
Using the Code
现在,如何使用这些类。例如,请看下面的代码。它显示了如何使用 SingleObjectLock
。SingleObjectLock
使用 WaitForSingleObject
,而 MultiObjectLock
使用 WaitForMultipleObjects
和 MsgWaitForMultipleObjects
(如果 UI 标志开启)。
// 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
}
}
MultiObjectLock
需要一个辅助类 LockHolder
,它管理同步对象容器。由于 MultiObjectLock
的目的是作为堆栈变量运行,因此 LockHolder
对象可以重复使用。这在 UI 线程中等待时特别有用,因为我们必须在处理消息时循环。对 MsgWaitForMultipleObjects
的调用被封装在 MultiObjectLock
构造函数中。它在析构函数中释放被信号化的对象。HasMessageInQueue()
函数可用于查看 MsgWaitForMultipleObjects
是否因队列中的消息而返回。如果它返回 false
,则表示一个对象已被信号化,我们可以退出循环。
这是如何使用 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());
}
}
下面是如何从 UI 线程使用 MultiObjectLock
// 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;
}
}
}
总结
我准备了一个演示应用程序,模拟了错误情况。这将有助于快速理解预期用法。MultiObjectLock
最多支持 MAXIMUM_WAIT_OBJECTS
。如果您想支持更多对象,则必须使用创建多个线程并等待线程句柄的技术。我希望这些类能有所帮助,并且更符合面向对象处理线程同步的方式。我非常期待看到它是如何被使用的。感谢您的耐心阅读。
历史
- 2013 年 2 月 10 日 - 作为一组类发布
- 2013 年 3 月 18 日 - 发布为 DLL 并进行了一些小的修复。演示应用程序使用此库。