等待事件的线程并不总是捕获事件信号

时间:2011-05-06 20:51:51

标签: c++ windows multithreading event-handling

我有一个应用程序,其中多个线程在同一个事件对象上等待发出信号。我看到的问题似乎是一种竞争条件,因为有时一些线程的等待状态(WaitForMultipleObjects)由于事件信号而返回,而其他线程的等待状态显然看不到事件信号因为他们不回来这些事件是使用CreateEvent作为手动重置事件对象创建的。

我的应用程序处理这些事件,以便在发出事件对象信号时,其“所有者”线程负责重置事件对象的信号状态,如下面的代码片段所示。等待同一事件的其他线程不会尝试重置其信号状态。

switch ( dwObjectWaitState = ::WaitForMultipleObjects( i, pHandles, FALSE, INFINITE ) )
{
case WAIT_OBJECT_0 + BAS_MESSAGE_READY_EVT_ID:
    ::ResetEvent( pHandles[BAS_MESSAGE_READY_EVT_ID] );
    /* handles the event */
    break;
}

换句话说,我看到的问题似乎是Remarks section for PulseEvent on the MSDN website中描述的问题:

  

如果发生对PulseEvent的调用   在线程有的时候   已经从等待状态中删除了   线程不会被释放,因为   PulseEvent只释放那些线程   现在正在等待   调用。因此,PulseEvent是   不可靠,不应该被使用   新的申请。相反,使用   条件变量。

如果发生这种情况,我可以看到的唯一解决方案是每个线程使用该对象的所有者线程注册其对给定事件对象的使用,以便所有者线程可以确定何时可以安全地重置事件对象的信号状态。

有更好的方法吗?感谢。

3 个答案:

答案 0 :(得分:2)

是的,有更好的方法:

  

[...]而是使用条件变量。

http://msdn.microsoft.com/en-us/library/ms682052(v=vs.85).aspx

具体查找WakeAllConditionVariable

答案 1 :(得分:2)

在PulseEvent描述中使用条件变量。唯一的问题是Windows上的本机条件变量是从Vista开始实现的,因此像XP这样的旧系统没有它。但是您可以使用其他一些同步对象(http://www1.cse.wustl.edu/~schmidt/win32-cv-1.html)来模拟条件变量,但我认为最简单的方法是使用boost库中的条件变量及其notify_all方法来唤醒所有线程(http://www.boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref

另一种可能性(但不是很漂亮)是为每个线程创建一个事件,现在你有了PulseEvent,你可以为所有线程调用SetEvent。对于此解决方案,自动重置事件可能会更好。

答案 2 :(得分:1)

为什么PulseEvent()不可靠,如果没有它

自动重置事件为王!

PulseEvent只出现在Windows NT 4.0中。它在原始Windows NT 3.1中不存在。相反,从Windows NT开始就存在可靠的函数,如CreateEvent,SetEvent和WaitForMultipleObjects,因此请考虑使用它们。

CreateEvent函数具有bManualReset参数。如果此参数为TRUE,则该函数将创建手动重置事件对象,该对象需要使用ResetEvent函数将事件状态设置为无信号。这不是你需要的。如果此参数为FALSE,则该函数会创建一个自动重置事件对象,系统会在释放一个等待线程后自动将事件状态重置为无信号。

这些自动重置事件非常可靠且易于使用。

如果等待具有WaitForMultipleObjects或WaitForSingleObject的自动重置事件对象,它会在退出这些等待函数时可靠地重置事件。

因此,请按以下方式创建活动:

EventHandle := CreateEvent(nil, FALSE, FALSE, nil);

等待来自一个线程的事件并从另一个线程执行SetEvent。这非常简单且非常可靠。

不要'曾经调用ResetEvent(因为它自动重置)或PulseEvent(因为它不可靠和不赞成)。甚至微软也承认不应该使用PulseEvent。见https://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx

此函数不可靠,不应使用,因为只有那些线程会被通知在"等待"现在调用PulseEvent的状态。如果它们处于任何其他状态,则不会通知它们,并且您可能永远不会确定线程状态是什么。等待同步对象的线程可以通过内核模式异步过程调用暂时从等待状态中删除,然后在APC完成后返回到等待状态。如果在线程从等待状态中删除期间发生对PulseEvent的调用,则不会释放该线程,因为PulseEvent仅释放那些在被调用时正在等待的线程。

您可以在以下链接中找到有关内核模式异步过程调用的更多信息:

我们从未在我们的应用程序中使用过PulseEvent。至于自动重置事件,我们从Windows NT 3.51开始使用它们,它们运行良好。

多个线程等待单个对象时该怎么办

不幸的是,你的情况有点复杂。您有多个线程在等待事件,您必须确保所有线程确实接收到通知。除了为每个线程创建自己的事件之外,没有其他可靠的方法。

你写了#at;我能看到的唯一解决方案是每个线程用该对象的所有者线程"来注册它对给定事件对象的使用。这是正确的。

您还写过"所有者线程可以确定何时可以安全地重置事件对象的信号状态" - 这是不切实际和不安全的。最好的方法是使用自动重置事件,这样它们就会自动重置。

因此,您需要拥有与线程一样多的事件。除此之外,您还需要保留已注册线程的列表。因此,要通知所有线程,您必须在循环中为所有事件句柄执行SetEvent。这是一种非常快速,可靠和廉价的方式。事件比线程便宜得多。因此,线程数是一个问题,而不是事件的数量。内核对象几乎没有限制 - 内核句柄的每进程限制为2 ^ 24。