none
请教一个关于windows内核对象“事件”的问题 RRS feed

  • 问题

  • 如果使用API CreateEvent()创建一个手动事件对象,示例代码:

        hEvent = CreateEvent(NULL, TRUE, FAlSE, NULL);

    然后有ABC三个线程调用WaitForSingleObject()等待这个事件对象触发,每个线程在等待成功返回后会调用ResetEvent()把事件状态重置为未触发状态。ABC三个线程的示例代码:

        WaitForSingleObject(hEvent);

        //... do something

        ResetEvent();

    在ABC三个线程处于等待状态后,另外一个线程调用SetEvent() 触发这个事件。此时,ABC三个线程都将被唤醒,还是其中一个或多个被唤醒?

     

    在MSDN中,SetEvent()的文档说:

    The state of a manual-reset event object remains signaled until it is set explicitly to the nonsignaled state by theResetEvent function. Any number of waiting threads, or threads that subsequently begin wait operations for the specified event object by calling one of the wait functions, can be released while the object's state is signaled.

    文档说,只要事件状态处于触发状态,那么任何等待线程都将被唤醒。这有两种可能的解释

    (1)在ResetEvent()调用之前调用的WaitForSingleObject()都将返回(即使它们还没有被调度 )。这种情况,ABC线程无论如何都将结束等待状态。

    (2)在ResetEvent()调用之前,所在线程已被调度的WaitForSingleObject()都将返回,但在ResetEvent()被调用之前未被调度的WaitForSingleObject()将继续等待。这种情况,ABC中至少有一个线程能够结束等待状态,也可能有多个或全部线程结束等待状态。

    那么,哪种解释是正确的呢?

    谢谢!

     

    2010年4月15日 9:13

答案

  • 答案就像版主说的, 如果是手工重置事件, 则所有等待都会被激活, 如果是自动重置事件, 系统则会按照算法选择一个等待使之激活.

    如果你想通过上述程序来解答疑惑, 建议你对每个 ResetEvent 操作进行标记已确定他们同 WaitForSingleObject 或者类似等待函数之间的执行顺序关系.

    ---------------------------------------------------

    你的疑惑是建立在线程所分得的单个时间片足够长的基础上, 使得它可以在一个时间片内完成等待和重置这两个操作. 而这一点我想是几乎不可能满足的.

    • 已标记为答案 Nancy Shao 2010年4月26日 10:12
    2010年4月16日 15:36
  • 你使用的手动Reset的Event当然是三个同时被唤醒了(前提是三个线程都在WaitForSingleObject这里等待),因为WaitForSingleObject只管你的Event的状态,他不管到底有几个线程在用这个Event.

    两种解释都正确。只不过第二种考虑的更全面一点


    0xBAADF00D
    • 已标记为答案 Nancy Shao 2010年4月26日 10:11
    2010年4月15日 12:16
    版主

全部回复

  • 你使用的手动Reset的Event当然是三个同时被唤醒了(前提是三个线程都在WaitForSingleObject这里等待),因为WaitForSingleObject只管你的Event的状态,他不管到底有几个线程在用这个Event.

    两种解释都正确。只不过第二种考虑的更全面一点


    0xBAADF00D
    • 已标记为答案 Nancy Shao 2010年4月26日 10:11
    2010年4月15日 12:16
    版主
  • 我想你可能没有理解我的问题,这两种解释是不可能同时成立的

    如果从自动事件来理解,SetEvent()调用之后,只有一个调用WaitForSingleObject()的线程被唤醒,并且事件自动被重置为未触发状态。在这个过程中,被唤醒的线程还没被调度,等其被调度时,事件也已经是未触发状态了。因此,我倾向于第一种解释的正确的。我想到了如何用程序来验证,试试先。

    2010年4月15日 12:40
  • 写了一个程序来验证。如果这个程序出现死锁,那么说明解释2是正确的;否则不能说明问题(如果时间足够长,则可以认为1是正确的)。

    程序运行了三个多小时,一直没有出现死锁。明天再多跑些时间看看如何。

    现在基本上可以认为1是正确的。我们也可以从PulseEvent()的行为来推出这个结论。

    这个问题的本质是Windows事件机制是如何实现的。如果了解了实现机制,这个问题就解决了。

    #include <windows.h>
    #include <iostream>
    #include <process.h>
    
    using namespace std;
    
    
    void TestFunc(int i);
    
    unsigned WINAPI ThreadA(void *pParam);
    unsigned WINAPI ThreadB(void *pParam);
    unsigned WINAPI ThreadC(void *pParam);
    
    
    HANDLE g_hEvent;
    HANDLE g_hEventA;
    HANDLE g_hEventB;
    HANDLE g_hEventC;
    
    
    int main()
    {
      for (int i = 1; ; ++i)
      {
        TestFunc(i);
        Sleep(10);
      }  
    
      return 0;
    }
    
    
    void TestFunc(int i)
    {
      cout << "Test No." << i << " begin..." << endl;
    
      g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
      g_hEventA = CreateEvent(NULL, FALSE, FALSE, NULL);
      g_hEventB = CreateEvent(NULL, FALSE, FALSE, NULL);
      g_hEventC = CreateEvent(NULL, FALSE, FALSE, NULL);
      
      HANDLE threadA = (HANDLE)_beginthreadex(NULL, 0, ThreadA, 0, 0, NULL);
      HANDLE threadB = (HANDLE)_beginthreadex(NULL, 0, ThreadB, 0, 0, NULL);
      HANDLE threadC = (HANDLE)_beginthreadex(NULL, 0, ThreadC, 0, 0, NULL);
    
      WaitForSingleObject(g_hEventA, INFINITE);
      WaitForSingleObject(g_hEventB, INFINITE);
      WaitForSingleObject(g_hEventC, INFINITE);
    
      cout << "Set event\n" << flush;
    
      SetEvent(g_hEvent);
      ResetEvent(g_hEvent);
    
      WaitForSingleObject(threadA, INFINITE);
      WaitForSingleObject(threadB, INFINITE);
      WaitForSingleObject(threadC, INFINITE);
    
      CloseHandle(g_hEvent);
      CloseHandle(g_hEventA);
      CloseHandle(g_hEventB);
      CloseHandle(g_hEventC);
    
      CloseHandle(threadA);
      CloseHandle(threadB);
      CloseHandle(threadC);
    
      cout << "Test No." << i << " finished.\n" << endl;
    }
    
    
    unsigned WINAPI ThreadA(void *pParam)
    {
      cout << "Thread A begin...\n" << flush;
    
      SignalObjectAndWait(g_hEventA, g_hEvent, INFINITE, FALSE);
      ResetEvent(g_hEvent);
    
      cout << "Thread A end.\n" << flush;
    
      return 0;
    }
    
    
    unsigned WINAPI ThreadB(void *pParam)
    {
      cout << "Thread B begin...\n" << flush;
    
      SignalObjectAndWait(g_hEventB, g_hEvent, INFINITE, FALSE);
      ResetEvent(g_hEvent);
    
      cout << "Thread B end.\n" << flush;
    
      return 0;
    }
    
    
    unsigned WINAPI ThreadC(void *pParam)
    {
      cout << "Thread C begin...\n" << flush;
    
      SignalObjectAndWait(g_hEventC, g_hEvent, INFINITE, FALSE);
      ResetEvent(g_hEvent);
    
      cout << "Thread C end.\n" << flush;
    
      return 0;
    }

    2010年4月15日 15:12
  • 答案就像版主说的, 如果是手工重置事件, 则所有等待都会被激活, 如果是自动重置事件, 系统则会按照算法选择一个等待使之激活.

    如果你想通过上述程序来解答疑惑, 建议你对每个 ResetEvent 操作进行标记已确定他们同 WaitForSingleObject 或者类似等待函数之间的执行顺序关系.

    ---------------------------------------------------

    你的疑惑是建立在线程所分得的单个时间片足够长的基础上, 使得它可以在一个时间片内完成等待和重置这两个操作. 而这一点我想是几乎不可能满足的.

    • 已标记为答案 Nancy Shao 2010年4月26日 10:12
    2010年4月16日 15:36