談談PulseEvent的缺陷,你明白了嗎?
PulseEvent這個是API會釋放一個(如果手動重置開啟的話,則是多個)正在等待事件對象的線程,并將事件對象設置一個”未設置”的狀態。如果這個時候碰巧沒有任何線程在等待這個事件,則事件除了被設置為”未設置”狀態以外,不會發生任何其他事情。
但它的缺陷就在這里。
你怎么知道,你所認為的正在等待事件的線程就一定是”真的(正在等待)”?顯然我們不能使用如下的方法:
因為在上面的代碼中,激發信號和等待事件這兩個操作存在一個競爭條件。信號對象所激發的線程可能在你等待事件對象之前,就已經完成了所有工作并將激發出一個事件脈沖(PulseEvent)了。
你可以嘗試使用SignalObjectAndWait這個函數,它會將信號激發和等待合并到一個單獨的操作中。但是即使是這樣,你還是無法確定線程在脈沖發生時是否正在等待事件。
當一個線程等待事件時,設備驅動或者內核本身的一部分可能會借用線程來進行一些任務處理(通過內核模式APC)。在此期間,線程不處于等待狀態,因為設備驅動正在使用它。如果PulseEvent在線程借用時發生,則它不會從等待中喚醒,因為PulseEvent函數只會喚醒在PulseEvent發生時正在等待的線程。
用戶模式程序不僅無法阻止內核模式代碼對用戶模式程序線程執行此操作,甚至也沒辦法檢測它是否已經發生。
(你可能會看到這種事情發生的一個地方是,如果你將調試器附加到進程,因為調試器會執行諸如掛起和恢復線程之類的事情,這會導致內核 APC。)
因此,PulseEvent 函數是沒有什么用的,我們應該避免使用它。
它繼續存在只是為了向后兼容。
附加信息:與內核 APC 相關的整個業務還意味著,當你激發一個信號量、自動重置事件或其他在發出信號時釋放單個線程的同步對象時,你無法預測哪個線程將被喚醒。 如果一個線程被“借用”來服務內核 APC,那么當它返回到等待列表時,它“回到行尾”。 因此,等待內核對象的對象的順序是不可預測的并且不能依賴。
總結
請老老實實使用SetEvent/ResetEvent,簡單,實在!
最后
Raymond Chen的《The Old New Thing》是我非常喜歡的博客之一,里面有很多關于Windows的小知識,對于廣大Windows平臺開發者來說,確實十分有幫助。
本文來自:《PulseEvent is fundamentally flawed》