SignalObjectAndWait关于SetEvent和WaitForSingleObject的目的是什么?

时间:2013-08-17 13:12:37

标签: c++ windows multithreading winapi multiprocessing

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

根据MSDNSignalObjectAndWait比单独调用SetEventWaitForSingleObject更有效。它还指出:

  

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

我并不完全理解这句话,但似乎效率并不是我们需要SignalObjectAndWait的唯一原因。任何人都可以提供SetEvent + WaitForSingleObject无法提供SignalObjectAndWait提供的功能的方案吗?

3 个答案:

答案 0 :(得分:2)

MSDN解释的目的是确保在发出事件信号之前线程处于等待状态。如果你调用WaitForSingleObject,那么线程在一个等待状态,但你不能在调用SetEvent之前调用它,因为这将导致SetEvent仅在等待完成后发生 - 如果没有其他任何东西调用SetEvent则没有意义。

答案 1 :(得分:2)

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

  

与单独的函数调用(例如SignalObjectAndWait后跟SetEvent)相比,WaitForSingleObject函数提供了一种更有效的方式来发送一个对象,然后然后等待另一个对象。

当你SetEvent和另一个[特别是更高优先级的线程正在等待此事件,可能会发生线程调度程序控制远离信令线程。当线程接收到控制权时,它所执行的唯一事件是以下WaitForSingleObject调用,因此浪费了上下文切换这么小的事情。

使用SignalObjectAndWait你提示内核说“嘿,我会等待另一个事件,所以如果它对你有任何影响,不要过度反复使用上下文切换”。

答案 2 :(得分:1)

如您所知,Microsoft提供了以下示例,说明为什么我们可能需要SignalObjectAndWait,如果我们已经需要单独的SetEvent和WaitForSingleObject(引用Microsoft示例):

  

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

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

工作线程执行如下代码:

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

此算法流程存在缺陷,绝不应使用。我们不需要这样一种令人困惑的机制,在这种机制中,线程会相互通知,直到我们处于“竞争状态”。本例中的Microsoft本身创建了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. 由于作业线程在整个作业列表耗尽之前不会开始休眠,因此您将不再需要调度程序线程。只有在出现新作业时才需要调度程序线程,而不是在作业线程完成作业时。
  15. 重要提示:此方案完全基于自动重置事件。您将永远不需要调用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的初始版本中。