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》