簡體   English   中英

等待事件的線程並不總是捕獲事件信號

[英]Threads Waiting for Event Do Not Always Catch Event Signal

我有一個應用程序,其中多個線程在同一個事件對象上等待發信號。 我看到的問題似乎是一種競爭狀況,因為有時某些線程的等待狀態( WaitForMultipleObjects )由於事件信號而返回,而其他線程的等待狀態顯然看不到事件信號,因為它們沒有不回來。 這些事件是使用CreateEvent作為手動重置事件對象創建的。

我的應用程序處理這些事件,以便在信號通知事件對象時,其“所有者”線程負責重置事件對象的信號狀態,如以下代碼片段所示。 等待同一事件的其他線程不會嘗試重置其信號狀態。

switch ( dwObjectWaitState = ::WaitForMultipleObjects( i, pHandles, FALSE, INFINITE ) )
{
case WAIT_OBJECT_0 + BAS_MESSAGE_READY_EVT_ID:
    ::ResetEvent( pHandles[BAS_MESSAGE_READY_EVT_ID] );
    /* handles the event */
    break;
}

換句話說,我看到的問題似乎是MSDN網站上PulseEvent的“ 備注”部分中描述的問題:

如果在從等待狀態中刪除線程的時間內發生對PulseEvent的調用,則不會釋放該線程,因為PulseEvent僅釋放那些在被調用時正在等待的線程。 因此,PulseEvent不可靠,不應被新的應用程序使用。 而是使用條件變量。

如果發生這種情況,我看到的唯一解決方案是每個線程向該對象的所有者線程注冊其對給定事件對象的使用,以便所有者線程可以確定何時可以安全地重置事件對象的信號狀態。

有一個更好的方法嗎? 謝謝。

為什么PulseEvent()不可靠以及沒有它怎么辦

自動重置事件為王!

PulseEvent僅出現在Windows NT 4.0中。 它在原始Windows NT 3.1中不存在。 相反,可靠的功能(例如CreateEvent,SetEvent和WaitForMultipleObjects)從Windows NT開始就存在,因此請考慮使用它們。

CreateEvent函數具有bManualReset參數。 如果此參數為TRUE,則該函數將創建一個手動重置事件對象,該對象需要使用ResetEvent函數將事件狀態設置為非信號狀態。 這不是您所需要的。 如果此參數為FALSE,則該函數將創建一個自動重置事件對象,並且在釋放單個等待線程之后,系統會自動將事件狀態重置為非信號狀態。

這些自動重置事件非常可靠且易於使用。

如果您等待帶有WaitForMultipleObjects或WaitForSingleObject的自動重置事件對象,則在退出這些等待函數時,它將可靠地重置事件。

因此,可以通過以下方式創建事件:

EventHandle := CreateEvent(nil, FALSE, FALSE, nil);

等待一個線程中的事件,然后從另一個線程中執行SetEvent。 這是非常簡單且非常可靠的。

永遠不要調用ResetEvent(因為它會自動重置)或PulseEvent(因為它不可靠且已棄用)。 甚至Microsoft也承認不應使用PulseEvent。 參見https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms684914(v=vs.85).aspx

此函數不可靠,不應使用,因為在調用PulseEvent時僅會通知處於“等待”狀態的那些線程。 如果它們處於任何其他狀態,則不會通知它們,並且您可能永遠無法確定線程狀態是什么。 可以通過內核模式異步過程調用將等待同步對象的線程暫時從等待狀態中刪除,然后在APC完成后返回到等待狀態。 如果在從等待狀態中刪除線程的時間內發生對PulseEvent的調用,則不會釋放該線程,因為PulseEvent僅釋放那些在被調用時正在等待的線程。

您可以在以下鏈接中找到有關內核模式異步過程調用的更多信息:

我們從未在應用程序中使用過PulseEvent。 關於自動重置事件,我們從Windows NT 3.51開始就使用它們,它們工作得很好。

當多個線程等待單個對象時該怎么辦

不幸的是,您的情況要復雜一些。 您有多個線程在等待事件,並且您必須確保所有線程實際上都收到了通知。 除了為每個線程創建自己的事件外,沒有其他可靠的方法。

您寫了atat“我唯一能看到的解決方案是每個線程向該對象的所有者線程注冊其對給定事件對象的使用”。 這是對的。

您還寫道:“所有者線程可以確定何時可以安全地重置事件對象的信號狀態”-這是不切實際且不安全的。 最好的方法是使用自動重置事件,這樣它們將自動重置自己。

因此,您將需要擁有與線程一樣多的事件。 除此之外,您將需要保留已注冊線程的列表。 因此,要通知所有線程,您將必須為所有事件句柄循環執行SetEvent。 這是一種非常快速,可靠且便宜的方法。 事件比線程便宜得多。 因此,線程數是一個問題,而不是事件的數目。 內核對象幾乎沒有限制-內核句柄的每個進程限制為2 ^ 24。

是的,有更好的方法:

[...]而是使用條件變量。

http://msdn.microsoft.com/zh-CN/library/ms682052(v=vs.85).aspx

具體查找WakeAllConditionVariable

如PulseEvent說明中所述,使用條件變量。 唯一的問題是Windows上的本機條件變量是從Vista開始實施的,因此XP之類的較早系統沒有此條件。 但是您可以使用其他一些同步對象( http://www1.cse.wustl.edu/~schmidt/win32-cv-1.html )來模擬條件變量,但是我認為最簡單的方法是使用boost庫中的條件變量其notify_all方法來喚醒所有線程( http://www.boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref

另一種可能性(但不是很漂亮)是為每個線程創建一個事件,並且當您現在擁有PulseEvent時,可以為所有線程調用SetEvent。 對於此解決方案,自動重置事件可能會更好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM