我想为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调用的信息将丢失。
在这种情况下,可能会发生以下情况:
有两种解决方法:经典的Comp.Sci方法是使用信号量而不是事件-信号量本质上是指所有“Set”调用都占满的事件。相反,您可以将事件视为最大计数为1的信号量,而忽略该信号以外的任何其他信号。
另一种方法是继续使用事件,但是当工作线程唤醒时,它只能假定队列中可能有一些项目,并且应在返回等待之前尝试处理所有这些项目-通常是通过放置代码它会在循环中弹出该项目,然后再弹出该项目并对其进行处理,直到其为空。现在,该事件不用于计数,而是表示“队列不再为空”。 (请注意,执行此操作时,还会得到以下情况:在处理队列的同时,还处理刚刚添加的项目以及为其调用SetEvent的项目,以便当工作线程到达WaitForSingleObject时,该线程醒来,但发现该队列已为空,因为该项目已被处理;乍一看似乎有点令人惊讶,但实际上很好。)
我认为这两个基本相同。两者都有次要的利弊,但它们都是正确的。 (我个人更喜欢事件方法,因为它使“需要完成的事情”或“更多数据可用”的概念与工作或数据的数量脱钩。)