【问题标题】:The algorithm for synchronizing between WaitForSingleObject() and SetEvent() functions?WaitForSingleObject() 和 SetEvent() 函数之间的同步算法?
【发布时间】:2012-03-23 03:33:36
【问题描述】:

我想为 2 个线程实现一个消息队列。线程#1 将弹出队列中的消息并处理它。线程 #2 将消息推送到队列中。

这是我的代码:

 Thread #1 //Pop message and process
 {
    while(true)
    {
        Lock(mutex);
        message = messageQueue.Pop();
        Unlock(mutex);

        if (message == NULL) //the queue is empty
        {
           //assume that the interruption occurs here (*)
            WaitForSingleObject(hWakeUpEvent, INFINITE);
            continue;
        }
        else
        {
            //process message
        }
    }
}

Thread #2 //push  new message in queue and wake up thread #1
{
    Lock(mutex);
    messageQueue.Push(newMessage)
    Unlock(mutex);

    SetEvent(hWakeUpEvent);
}

问题是有些情况下SetEvent(hWakeUpEvent)会在WaitForSingleObject()之前被调用(注(*)),这会很危险。

【问题讨论】:

  • 搜索并发有界队列。它只是你的问题。
  • “会很危险”是什么意思?
  • 我认为你描述的不是问题,因为如果首先调用 SetEvent,WaitForSingleObject 根本不会休眠,而是立即返回,这样你的循环就会继续下一次迭代。请参阅 msdn“手动重置事件对象的状态保持信号状态,直到它被 ResetEvent 函数显式设置为非信号状态。任意数量的等待线程,或随后通过调用其中之一开始对指定事件对象的等待操作的线程等待函数,可以在对象状态发出信号时释放。"
  • @Nawaz:谢谢,我稍后试试^.^
  • 手动或自动无关紧要,@David。只要您不使用PulseEvent,该事件就会一直发出信号,直到有人等待为止。鉴于此处的代码,该事件需要是一个自动重置事件,因为没有任何东西会调用ResetEvent

标签: c++ multithreading winapi


【解决方案1】:

你的代码没问题!

SetEvent 和 WaitForSingleObject 之间的计时没有实际问题:关键问题是事件上的 WaitForSingleObject 会检查事件的状态,并等待它被触发。如果事件已经被触发,它将立即返回。 (从技术上讲,它是水平触发的,而不是边缘触发的。)这意味着如果在调用 WaitForSingleObject 之前或期间调用 SetEvent 就可以了;无论哪种情况,WaitForSingleObject 都会返回;立即或稍后调用 SetEvent 时。

(顺便说一句,我假设在这里使用自动重置事件。我想不出使用手动重置事件的充分理由;您最终不得不在 WaitForSingleObject 返回后立即调用 ResetEvent;并且有一个危险,如果你忘记了这一点,你最终可能会等待一个你已经等待但忘记清除的事件。此外,在检查基础数据状态之前重置很重要,否则如果在数据被调用之间调用 SetEvent处理并调用 Reset(),您会丢失该信息。坚持使用自动重置,您可以避免这一切。)

--

[编辑:我将 OP 的代码误读为在每次唤醒时执行一次“弹出”,而不是只等待空,因此下面的 cmets 指的是该场景的代码。 OP 的代码实际上等同于下面的第二个建议修复。因此,下面的文字实际上描述了一个有些常见的编码错误,其中事件被用作信号量,而不是 OP 的实际代码。]

但是这里有一个不同的问题[或者,如果每次等待只有一个弹出...],那就是 Win32 事件对象只有两种状态:无信号和有信号,所以你只能使用它们跟踪二进制状态,但不计数。如果您 SetEvent 和已发出信号的事件,它仍保持已发出信号,并且该额外 SetEvent 调用的信息将丢失。

在这种情况下,可能发生的情况是:

  • 项目已添加,SetEvent 已调用,事件现在已发出信号。
  • 添加了另一个项,再次调用 SetEvent,事件保持信号状态。
  • 工作线程调用WaitForSingleObject,返回,清除事件,
  • 只处理一项,
  • 工作线程调用 WaitForsingleObject,由于事件未发出信号而阻塞,即使队列中仍有项目。

有两种解决方法:经典的 Comp.Sci 方法是使用信号量而不是事件 - 信号量本质上是计算所有“Set”调用的事件;相反,您可以将事件视为最大计数为 1 的信号量,它会忽略除此之外的任何其他信号。

另一种方法是继续使用事件,但是当工作线程唤醒时,它只能假设队列中可能有一些项,它应该在之前尝试处理它们它返回等待 - 通常通过将弹出项目的代码放入一个循环中,该循环弹出项目并处理它们直到它为空。该事件现在不用于计数,而是表示“队列不再为空”。 (请注意,执行此操作时,您还可能遇到这样的情况,即在处理队列时,您还处理了刚刚添加并为其调用了 SetEvent 的项目,因此当工作线程到达 WaitForSingleObject 时,线程唤醒但发现队列为空,因为该项目已被处理;这起初看起来有点令人惊讶,但实际上很好。)

我认为这两者大致相同;两者都有一些小的优点和缺点,但它们都是正确的。 (我个人更喜欢事件方法,因为它将“需要完成的事情”或“有更多可用数据”的概念与工作或数据的数量分离。)

【讨论】:

  • 非常感谢。你的解释很清楚。您提出的上述两种方式都是好方法。在我上面的代码中,在调用 WaitForSingleObject() 之前检查了队列处于空状态,所以我认为它工作正常。
  • 我同意所写的循环不会遇到所描述的问题,因为它只是在观察到消息队列为空之后才等待事件。尽管如此,对潜在陷阱以及如何避免它的解释是好的。
  • @Vincent Povirk:谢谢,但我在上面的代码中没有看到任何“潜在的陷阱”。你能告诉我那个案子吗?再次感谢。
  • @TTGroup: 拍脑袋 - 是的,你说得对;我习惯于将模式视为无限循环中的循环直到空,因此错过了您的代码等效的事实( - 这就是我在深夜回答的结果!)。我将编辑我的答案以澄清这一点。
  • 谢谢,你太棒了,BrendanMcK ^^
【解决方案2】:

“经典”方式(即肯定会正常工作)是使用信号量(参见 CreateSemaphore、ReleaseSemaphore API)。创建信号量为空。在生产者线程中,锁定互斥体,推送消息,解锁互斥体,释放一个单元给信号量。在消费者线程中,使用 WFSO 等待信号量句柄(就像您等待上面的事件一样),然后锁定互斥体,弹出消息,解锁互斥体。

为什么这比事件更好?

1) 无需检查队列计数 - 信号量对消息进行计数。

2) 信号量的信号不会因为没有线程在等待它而“丢失”。

3) 不检查队列计数意味着这种检查的结果和所采用的代码路径不会因为抢占而不正确。

4) 它适用于多个生产者和多个消费者而无需更改。

5) 更加跨平台友好 - 所有抢占式操作系统都有互斥体/信号量。

【讨论】:

  • 谢谢,我认为这将是在这种情况下申请的好方法。但我可能会保留我上面的代码,以便简单^.^
  • +1 - 尽管注意 (2) 也适用于事件;只要您使用 SetEvent 而不是 PulseEvent,事件信号也不会丢失,因为没有线程在等待它。
  • @BrendanMcK - 是的,事件丢失了。向一个空信号量发出信号一次,它的计数变为 1。向一个空信号量发出 100 次信号,它的计数变为 100。事件 - 无论你发出多少次信号,它都保持设置 - 它没有计数。
  • @MartinJames:知道了;我误解了你的意思;也许问题在于您的答案中的措辞有些模棱两可:问题在于事件没有计算;但是,如果您在没有线程等待的情况下发出一个未发出信号的事件,则第一个信号不会丢失(尽管它仍然没有被计算在内,因此 additional 信号会丢失)。编辑 (2) 答案以明确提及计数可能会很有用。
【解决方案3】:

如果有多个线程同时使用数据,或者您使用PulseEvent 而不是SetEvent,则会很危险。

但是只有一个消费者,并且由于事件会一直发出信号,直到您等待(如果自动休息)或永远(如果手动重置),它应该可以正常工作。

【讨论】:

    猜你喜欢
    • 2020-03-25
    • 2013-08-19
    • 2019-03-20
    • 2016-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 2015-07-25
    相关资源
    最近更新 更多