当我们调用 CreateEvent 函数创建一个事件对象的时候,我们可以通过参数来指定这个事件对象是自动重置的,还是手动重置的。
对于一个手动重置的事件对象,它很容易理解:当事件未激发时,在此事件上的等待将一直挂起,而当事件被激发时,在此事件对象上的等待将会立即返回。上面的工作原则和有多少个线程正在等待此对象没有任何关系。所有线程对此对象的等待操作都是一致的,并且事件对象的状态也不会受到等待它的线程数量影响。
对于一个自动重置的事件对象,事情开始变得复杂了。
理解它的工作原理的最简单方法是:将它看做是一个最大计数为 1 的信号量。
此话怎讲?
当事件未激发时,在此事件上的等待的线程将一直挂起,而当事件被激发时,仅会只有一个等待线程结束等待,并且事件对象将会自动重置其状态为未激发态。结果就是:剩下的其他线程将会继续等待。从我们之前对 PulseEvent 的讨论来看,你可能已经知道了,如果有多个等待线程,则不确定将释放哪个等待线程。
使用自动重置事件的陷阱在于:你设置了已处于激发态的事件。由于事件只有两种状态(设置和重置),因此设置已设置的事件不起作用。如果使用事件来控制资源生产者/使用者模型,则设置已处于激发态的事件将导致你看起来 “丢失:了令牌。
请考虑以下场景模式。
但是,如果时机没有完全出来怎么办?如果使用者线程完成工作有点慢(或者生产者线程生成它的速度有点快),该怎么办:
请注意,生成者生成了三个工作项,但使用者只执行了其中的两个。第三个 SetEvent 没有效果,因为事件已经设置好了。(如果尝试将信号量的令牌计数增加到超过其最大值,则会遇到相同的问题。)如果希望唤醒数与集数匹配,则需要使用最大令牌计数与将支持的最大未完成工作项数一样高的信号量。
总结
了解你手上工具的使用方法,更加需要了解其局限性。永远在正确的场景下使用正确的工具,这确实挺难的。