我想为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()(note(*))之前会被调用,这很危险。

最佳答案

您的代码很好!

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

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

-

[编辑:我误读了OP的代码,因为每次唤醒时都执行一次“弹出”操作,而不是仅在空时等待,因此以下注释指的是该场景的代码。 OP的代码实际上等效于下面第二个建议的修复程序。因此,下面的文字实际上描述了某种常见的编码错误,在该错误中,事件被当作信号量使用,而不是OP的实际代码。]

但是这里有一个不同的问题[或者,如果每次等待只有一个弹出窗口……],那就是Win32 Events对象只有两种状态:未信号和已信号通知,因此您只能使用它们来跟踪二进制文件状态,但不包括在内。如果您已设置信号的SetEvent和事件,则它仍保持已信号状态,并且该额外SetEvent调用的信息将丢失。

在这种情况下,可能会发生以下情况:

  • 已添加项目,调用SetEvent,现在已发出事件信号。
  • 添加了另一个项目,再次调用SetEvent,事件保持信号状态。
  • 工作线程调用WaitForSingleObject,该对象返回,清除事件
  • 仅处理一项,
  • 工作线程调用WaitForsingleObject,该事件将阻塞,因为事件没有信号,即使队列中还有一个项目也是如此。

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

    另一种方法是继续使用事件,但是当工作线程唤醒时,它只能假定队列中可能有一些项目,并且应在返回等待之前尝试处理所有这些项目-通常是通过放置代码它会在循环中弹出该项目,然后再弹出该项目并对其进行处理,直到其为空。现在,该事件不用于计数,而是表示“队列不再为空”。 (请注意,执行此操作时,还会得到以下情况:在处理队列的同时,还处理刚刚添加的项目以及为其调用SetEvent的项目,以便当工作线程到达WaitForSingleObject时,该线程醒来,但发现该队列已为空,因为该项目已被处理;乍一看似乎有点令人惊讶,但实际上很好。)

    我认为这两个基本相同。两者都有次要的利弊,但它们都是正确的。 (我个人更喜欢事件方法,因为它使“需要完成的事情”或“更多数据可用”的概念与工作或数据的数量脱钩。)

    07-24 09:46