65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.27/5 (7投票s)

2013 年 2 月 9 日

CPOL

5分钟阅读

viewsIcon

42334

downloadIcon

784

自动线程同步类,它们抛出异常而不是错误代码。

引言

在面向对象的世界中,异常总是优于返回码。众所周知,这会使正常流程变得一目了然,并区分替代流程(异常)。这不仅提高了正常执行流程的效率(无需检查返回值),还使代码更清晰易懂。您可能会想,为什么我要喋喋不休地谈论这些众所周知的事实。嗯,我正要切入正题。让自动线程同步类在线程等待失败或超时时抛出异常怎么样?这就是我们今天要讨论的主题。

背景

这就是我们通常处理线程同步的方式,请看下面的代码

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 同步类,如 CMutexCEventCSemaphore 等,它们封装了 API。此外,像 CSinlgeLockCMultiLock 这样的关联类会自动获取和释放锁。但是 Lock 函数在等待成功或失败时返回 TRUEFALSE。在失败的情况下,无法获得确切的返回码。

请看下面使用 CSingleLock 的代码片段。

CSingleLock MyLock(&m_myMutex);

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

现在,我认为我们已经进入了情境,我将提出一个解决方案,以摆脱错误代码并改用异常。我提供的同步类在发生 wait 失败的情况时会抛出异常。

这将使代码更清晰,因为我们可以在单独的异常处理块中处理每个错误情况,例如 WAIT_FAILEDWAIT_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 类组提供使用 CriticalSectionEventMutexSemaphore 进行线程同步的设施。这些类可以与自动锁定和解锁对象(如 SingleObjectLockMultiObjectLock)一起使用。SynchronizerBase 是此类组的基类。当出现等待超时等情况时,这些类会抛出异常。众所周知,基于异常的机制可以避免在成功的情况下进行错误代码检查。它将正常流程和异常流程分开。

  • CriticalSectionSynchronizer - 此类封装了临界区。它作为自动锁定处理类 SingleObjectLock 的便利。SingleObjectLock 的构造函数调用 Lock,析构函数调用 UnLock
  • MutexSynchronizer - 此类封装了互斥体内核对象。它根据互斥体的创建或 WaitForSingleObject 函数的返回抛出 SyncObjectCreationFailedSyncObjectWaitFailedSyncObjectWaitTimedOutSyncObjectWaitAbandoned 异常。它充当 SingleObjectLockMultiObjectLock 类自动锁定获取和释放的便利。
  • SemaphoreSynchronizer - 此类封装了信号量内核对象。它根据信号量的创建或 WaitForSingleObject 函数的返回抛出 SyncObjectCreationFailedSyncObjectWaitFailedSyncObjectWaitTimedOut 异常。它充当 SingleObjectLockMultiObjectLock 类自动锁定获取和释放的便利。
  • SingleObjectLock - 此类通过使用构造函数和析构函数来处理自动锁定获取和释放。它通过在析构函数中调用 UnLock 来确保正确释放锁。它与单个对象等待函数 WaitForSingleObject 一起工作。
  • MultiObjectLock - 此类通过使用构造函数和析构函数来处理自动锁定获取和释放。它与多个对象等待函数一起工作,例如 WaitForMultipleObjectsMsgWaitForMultipleObjects。它根据等待函数的返回值抛出 SyncObjectWaitTimedOutSyncObjectWaitFailedSyncObjectWaitAbandoned 异常。此对象可用于在工作线程和 UI 线程中等待。如果指定了 UI 线程,它将在内部使用 MsgWaitForMultipleObjectsLockHolder 负责保留同步对象。同步对象应添加到 LockHolder 并传递给 MultiObjectLock 实例。LockHolder 可以与 MultiObjectLock 的本地实例重复使用。
  • LockHolder - 持有同步对象列表。它与 MultiObjectLock 一起使用。同步器列表是分开的,因为我们可以创建 MultiObjectLock 的本地实例,而 LockHolder 对象可以重复使用。因此,在创建另一个 MultiObjectLock 实例时,无需再次添加同步器对象。

Using the Code

现在,如何使用这些类。例如,请看下面的代码。它显示了如何使用 SingleObjectLockSingleObjectLock 使用 WaitForSingleObject,而 MultiObjectLock 使用 WaitForMultipleObjectsMsgWaitForMultipleObjects(如果 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 并进行了一些小的修复。演示应用程序使用此库。
© . All rights reserved.