【问题标题】:What's the purpose of SignalObjectAndWait regards there is SetEvent and WaitForSingleObject?SignalObjectAndWait 存在 SetEvent 和 WaitForSingleObject 的目的是什么?
【发布时间】:2013-08-19 19:11:16
【问题描述】:

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

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

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

这句话我没完全看懂,不过看来效率并不是我们需要SignalObjectAndWait的唯一原因。谁能提供SetEvent + WaitForSingleObject 无法提供SignalObjectAndWait 提供的功能的场景?

【问题讨论】:

    标签: c++ windows multithreading winapi multiprocessing


    【解决方案1】:

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

    【讨论】:

    • 但是 MSDN 说:“请注意,“信号”和“等待”不能保证作为原子操作执行。在其他处理器上执行的线程可以在调用 SignalObjectAndWait 的线程开始等待第二个对象。"
    • @ZijingWu:好的,我已经编辑了我的答案以涵盖它不是原子的事实。
    【解决方案2】:

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

    SetEvent 后跟WaitForSingleObject 等单独的函数调用相比,SignalObjectAndWait 函数提供了一种更有效的方式来向一个对象发出信号并然后等待另一个对象。

    当你 SetEvent 和另一个 [esp.更高优先级]线程正在等待此事件,线程调度程序可能会从信号线程中夺走控制权。当线程接收回控制权时,它所做的唯一 事情就是下面的WaitForSingleObject 调用,因此浪费了这么小的事情的上下文切换。

    使用SignalObjectAndWait,您可以通过说“嘿,无论如何我都会等待另一个事件,所以如果它对您有任何影响,请不要过度地来回切换上下文”来暗示内核。

    【讨论】:

      【解决方案3】:

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

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

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

      工作线程执行如下代码:

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

      此算法流程存在缺陷,不应使用。我们不需要这种令人费解的机制,在我们处于“竞争条件”之前,线程会相互通知。在此示例中,Microsoft 自己创建了 Race Condition。工作线程应该只是等待一个事件并从列表中获取任务,而生成任务的线程应该只是将任务添加到该列表并发出事件信号。因此,我们只需要一个事件,而不是上面 Microsoft 示例中的两个事件。该列表必须由临界区保护。生成任务的线程不应等待工作线程完成任务。如果有任务需要在完成时通知某人,这些任务应该自己发送通知。换句话说,是任务在完成时通知线程——不是线程专门等待作业线程,直到它完成所有任务的处理。

      Microsoft 示例中的这种有缺陷的设计为诸如原子 SignalObjectAndWait 和原子 PulseEvent 之类的怪物创建了命令——最终导致厄运的函数。

      这是一个算法,您如何才能实现您在问题中设定的目标。只需简单的事件和简单的函数 SetEvent 和 WaitForSingleObject 即可实现目标 - 不需要其他函数。

      1. 为所有作业线程创建一个通用的自动重置事件,以表明有一个(任务)可用;并且还创建每个线程的自动重置事件,每个作业线程一个事件。
      2. 多个作业线程,一旦完成运行所有作业,都使用 WaitForMultipleObjects 等待这个常见的自动重置“任务可用”事件 - 它等待两个事件 - 公共事件和自己的线程事件。
      3. 调度程序线程将新的(待处理的)作业放入列表中。
      4. 作业列表访问必须受 EnterCriticalSection/LeaveCriticalSection 保护,因此没有人会以其他方式访问此列表。
      5. 每个作业线程在完成一项作业后,在开始等待自动重置“任务可用”事件及其自己的事件之前,检查待处理作业列表。如果列表不为空,则从列表中获取一项作业(将其从列表中删除)并执行。
      6. 必须有另一个受临界区保护的列表 - 等待作业线程列表。
      7. 在每个作业开始等待之前,即在它调用 WaitForMultipleObjects 之前,它会将自己添加到“等待”列表中。在退出等待时,它会从这个等待列表中删除自己。
      8. 当调度程序线程将新的(待处理的)作业放入作业列表时,它首先进入作业列表的关键部分,然后进入treads 列表 - 因此同时进入两个关键部分。但是,作业线程可能永远不会同时进入两个关键部分。
      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。

      【讨论】:

        猜你喜欢
        • 2020-03-25
        • 2012-03-23
        • 2019-03-20
        • 2016-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-17
        • 1970-01-01
        相关资源
        最近更新 更多