SignalObjectAndWait的目的是什么?有SetEvent和WaitForSingleObject?

我刚刚意识到Windows平台有SignalObjectAndWait API函数。 但是已经有SetEventWaitForSingleObject 。 您可以一起使用它们来实现与SignalObjectAndWait相同的目标。

基于MSDN , SignalObjectAndWait比单独调用SetEventWaitForSingleObject更高效。 它还指出:

线程可以使用SignalObjectAndWait函数确保工作线程在发送对象之前处于等待状态。

我不完全理解这句话,但效率并不是我们需要SignalObjectAndWait的唯一原因。 任何人都可以提供一个场景,其中SetEvent + WaitForSingleObject无法提供SignalObjectAndWait提供的function?

我的理解是,这个单一的功能在避免以下情况下更有效率。

SignalObjectAndWait函数提供了一种更有效的方式来发信号通知一个对象, 然后等待另一个对比单独的函数调用,比如SetEvent然后是WaitForSingleObject

当你你SetEvent和另一个[尤其是。 更高的优先级]线程正在等待这个事件,那么线程调度器可能会从控制线程中取出控制权。 当线程接收到控制WaitForSingleObject ,它唯一做的事就是下面的WaitForSingleObject调用,从而浪费了上下文切换以实现这样一个小事情。

使用SignalObjectAndWait你提示内核说:“嘿,我将等待另一个事件,所以如果它有什么区别,不要过度反弹,上下文切换”。

正如MSDN所解释的,目的是确保线程处于等待状态,然后事件被发送。 如果调用WaitForSingleObject,线程处于waitstate状态,但在调用SetEvent之前,不能调用SetEvent,因为只有在等待完成之后,才会发生SetEvent – 如果没有别的东西在调用SetEvent,这是毫无意义的。

正如你所知,微软给出了下面的例子,说明为什么我们可能需要SignalObjectAndWait,如果我们已经需要单独的SetEvent和WaitForSingleObject(引用微软的例子):

线程可以使用SignalObjectAndWait函数确保工作线程在发送对象之前处于等待状态。 例如,线程和工作线程可以使用事件对象的句柄来同步他们的工作。 线程执行代码如下:

 dwRet = WaitForSingleObject(hEventWorkerDone, INFINITE); if( WAIT_OBJECT_0 == dwRet) SetEvent(hEventMoreWorkToDo); 

工作线程执行如下代码:

 dwRet = SignalObjectAndWait(hEventWorkerDone, hEventMoreWorkToDo, INFINITE, FALSE); 

这个算法流程是有缺陷的,不应该被使用。 我们不需要这种线程通知对方的困惑机制,直到我们处于“竞争条件”。 在这个例子中,微软自己创建了Race Condition。 工作线程应该等待一个事件,并从列表中取出任务,而生成任务的线程应该只是向这个列表中添加任务并发信号通知事件。 所以,我们只需要一个事件,而不是像上面的Microsoft示例那样。 该名单必须由关键部分保护。 生成任务的线程不应该等待工作线程完成任务。 如果有任务需要通知某人完成,则任务应该自行发送通知。 换句话说,完成时会通知线程的任务 – 在完成处理所有任务之前,不是专门等待作业线程的线程。

像微软例子中的这样一个有缺陷的设计,为像原子SignalObjectAndWait和原子PulseEvent这样的怪物创造了迫切的需求,最终导致厄运。

这是一个算法,你怎么能达到你的目标设置在你的问题。 目标是通过简单而简单的事件来实现的,而简单的函数SetEvent和WaitForSingleObject则不需要其他函数。

  1. 为所有作业线程创建一个通用自动重置事件,以表示存在可用的任务(任务); 并创建每线程自动重置事件,每个作业线程一个事件。
  2. 多个作业脚本,一旦完成所有作业的运行,都使用WaitForMultipleObjects等待这个常见的自动重置“任务可用”事件 – 等待两个事件 – 公共事件和自己的线程事件。
  3. 调度程序线程将新的(挂起的)作业放到列表中。
  4. 作业列表访问必须由EnterCriticalSection / LeaveCriticalSection保护,所以没有人以其他方式访问此列表。
  5. 每个作业线程在完成一个作业之后,在开始等待自动重置“任务可用”事件及其自身事件之前,检查未决作业列表。 如果列表不为空,则从列表中获取一个作业(将其从列表中移除)并执行。
  6. 必须有关键部分保护的另一个列表 – 等待作业线程列表。
  7. 在每个作业开始等待之前,即在它调用WaitForMultipleObjects之前,它将自己添加到“等待”列表中。 退出等待时,它将从等待列表中删除。
  8. 当调度程序线程将新的(待处理的)作业放到作业列表中时,它首先进入作业列表的关键部分,然后进入作业列表的关键部分 – 因此同时输入两个关键部分。 然而,作业线程可能永远不会同时进入两个关键部分。
  9. 如果只有一个工作处于待处理状态,则调度程序会将通用自动重置事件设置为已发信号状态(调用SetEvent) – 哪个休眠作业线程将会接收作业并不重要。
  10. 如果有两个或两个以上的工作挂起,它不会发出公共事件的信号,但会计算有多少线程正在等待。 如果至少有多少线程正在等待作业,则将该线程数的自己事件标记为有事件,并让其余线程继续其休眠。
  11. 如果有比等待线程更多的作业,请为每个等待线程指示自己的事件。
  12. 在调度器线程发出所有事件的信号之后,它将离开关键部分 – 首先是线程列表,然后是作业列表。
  13. 在调度程序线程发出了特定情况下所需的所有事件之后,它会自动进入睡眠状态,即使用自己的睡眠事件(这也是一个自动重置事件,在新作业出现时应该发出信号)调用WaitForSingleObject。
  14. 由于作业线程在整个作业列表耗尽之前不会开始休眠,因此不再需要调度程序线程。 调度程序线程将只在以后需要,当出现一个新的工作,而不是作业线程完成一个工作时。

重要提示:该方案纯粹基于自动重置事件。 您将永远不需要调用ResetEvent。 所有需要的函数是:SetEvent和WaitForMultipleObjects(或WaitForSingleObject)。 不需要原子事件操作。

请注意:当我写了一个线程睡眠,它不会调用“睡眠”API调用 – 它不会被需要,它只是因为调用WaitForMultipleObjects(或WaitForSingleObject)在“等待”状态。

如您所知,自动重置事件,SetEvent和WaitForMultipleObjects函数非常可靠。 他们自NT 3.1以来就存在了。 你总是可以设计出一个完全依赖于这些简单函数的程序逻辑 – 所以你永远不需要假定像PulseEvent或SignalObjectAndWait这样的原子操作的复杂和不可靠的函数。 顺便说一句,SignalObjectAndWait只出现在Windows NT 4.0中,而SetEvent和WaitForMultipleObjects确实存在于最初版本的Win32 – NT 3.1中。