setEvent 在没有 ResetEvent 的情况下被调用

Posted

技术标签:

【中文标题】setEvent 在没有 ResetEvent 的情况下被调用【英文标题】:setEvent is called without ResetEvent 【发布时间】:2014-01-08 00:51:09 【问题描述】:

如果手动重置事件使用 setEvent 设置但未使用 ResetEvent 重置,会发生什么情况;并且该事件被多次触发。即在处理事件时,再次设置事件。

以下是示例任务:

void foo()

...
   SetEvent(hEvent1);
...

void foo1()

...
SetEvent(hEvent2);
...

int MainHandler()

...
 dwEvent = WaitForMultipleObjects(2,
 ghEvents,     // array of objects
 FALSE,       // wait for any object
 5000); 
switch(dwEvent)

case hEvent1:
//do something
break;
case hEvent2:
//do something
break;


现在,假设当 hEvent1 的案例正在执行时(即它仍然设置),不知何故再次触发了 hEvent1。我故意不放 ResetEvent(hEvent1) 即使它是手动复位事件。那么,我们有竞争条件吗?

【问题讨论】:

什么也没发生。这通常意味着您的代码出现故障。 请注意,您上面的代码假定WaitForMultipleObjects 返回一个事件句柄。它没有。有关解释返回值的正确方法,请参阅下面的答案。 【参考方案1】:

事件就像布尔标志 - 可以将 true 分配给它两次。没有人可能正在等待当前已发出信号的事件,因此当您再次将其设置为已发出信号时不会发生任何事情。

我不确定您所说的“正在处理事件”是什么意思。看来您两次使用“事件”一词有两种不同的含义 - 一个由 HANDLE 表示的内核对象,以及“我的程序必须做的事情”。

【讨论】:

【参考方案2】:

在您使用WaitForMultipleObjects 的示例中,如果您正在等待的事件未在事件句柄数组中按频率递增的顺序列出,您可能会遇到潜在问题。另请注意我的评论,您上面的代码假定 WaitForMultipleObjects 返回一个事件句柄。没有。

WaitForMultipleObjects 将在看到 first 信号事件时停止等待,从索引零向上查看数组。

因此,如果您有一个事件被设置(或未重置)为数组中的第一个条目,那么其他事件将被饿死(即永远不会被看到)。

因此,在您的示例中,只要hEvent1 仍然发出信号,hEvent2 就不会被看到。

作为一个常见模式的示例,假设我们有一些工作线程,其线程函数已被路由回某个包含事件对象和互斥锁或其他内容的所属类。工作线程只响应两个事件——一个整洁地关闭的请求和一个做一些工作的请求。代码可能如下所示:

UINT CMyClass::ThreadFunc()

    // make an array of some interesting event handles
    HANDLE hEvents[2] =  m_hKillEvent, m_hWorkEvent ;

    // main loop - do work until killed
    while (true)
    
        // wait for either event
        DWORD dwRet = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);

        // see why we got signalled
        if (dwRet == WAIT_OBJECT_0)
        
            // kill requested - exit loop
            break;
        
        else if (dwRet == WAIT_OBJECT_0 + 1)
        
            // work requested - do some work here
        
        else
        
            // error handling - bad handles?
        
    

    // normal exit
    return 0;

按照编码,这可以正常工作——主线程调用SetEvent(m_hWorkEvent) 来触发后台线程做一些工作,它调用SetEvent(m_hKillEvent) 来关闭工作线程。如果线程正在工作中,关闭可能会受到一些超时保护,例如:

// close worker thread
SetEvent(m_hKillEvent);

// wait for thread to die peacefully
DWORD dwRet = WaitForSingleObject(m_hWorkerThread, 5000);
if (dwRet == WAIT_TIMEOUT)

    // worker failed to respond - error handling here

现在,即使m_hWorkEvent 被非常频繁地发出信号,这个关闭过程也可以正常工作 - 例如,当do some work here 完成时,事件再次被发出信号。这是因为WaitForMultipleObjects 将始终检查杀死事件first,因为它是数组中的第一个。

但是,如果数组是这样定义的:

    // make an array of some interesting event handles
    HANDLE hEvents[2] =  m_hWorkEvent, m_hKillEvent ;

如果m_hWorkEvent 不断收到信号(例如,它在长时间运行的do some work here 期间再次设置,或者它是手动重置事件并且您从未重置它),那么线程将永远不会 em> 干净地退出,因为它永远不会看到终止信号。它总是会先尝试做一些工作。

这就是我所说的以频率递增的顺序对数组中的事件进行排序。 kill 事件的频率最低(它只发出一次信号),所以它首先发生。如果您针对不同的工作请求有三个或更多事件,则需要保持相同的顺序,否则某些事件会被饿死。

无论您决定做什么,同样值得注意的是,即使 WaitForMultipleObjects 因“错误”事件而被释放,您仍然可以通过等待零超时来检查特定事件是否发出信号:

if (WaitForSingleObject(hSomeEvent, 0) == WAIT_OBJECT_0)

    // ... hSomeEvent was signaled

这可以让您在长时间运行的后台工作过程的适当部分对终止事件进行中间检查。

【讨论】:

嗨罗杰,我面临着类似的情况,即应用程序进入“无响应”状态。您能否进一步解释一下频率及其解决方案? @user3171277 好的,我已经用一个常见的问题示例扩展了我的答案 - 也许这有帮助? @user3171277 不客气。如果您认为它回答了您的问题,也许您会考虑marking it as accepted。 我很想...但是我该怎么做呢? (对不起,这么愚蠢的问题,但我找不到怎么做...... :)) @user3171277 在答案的左上角,有向上/向下投票按钮,下方是大纲中的复选标记。只需单击它即可使其变为绿色。

以上是关于setEvent 在没有 ResetEvent 的情况下被调用的主要内容,如果未能解决你的问题,请参考以下文章

C++多线程同步技巧--- 事件

事件对象Event的使用

Windows 的 SetEvent 的确切行为是啥?

Windows setevent 处理

单个 SetEvent() 能否触发多个 WaitForSingleObject()

MyEvent.SetEvent; // 同步信号置位