《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“事件内核对象”

  本书首先介绍了一个重要的概念“成功的副作用”,这里笔者作一下简述。

  当调用WaitForSingleObject和WaitForMultipleObject函数成功之后,该函数在返回成功的时候,系统可能会自动更改所等待的内核对象的状态,即将其从“已通知状态”切换为“未通知状态”。

  当一个内核对象的状态被更改,称之为“成功等待的副作用”。比如,一个“自动重置”的事件内核对象,当调用等待函数成功返回的时候,该事件内核对象会由已通知状态转变为未通知状态。

  比如此时有一个自动重置的事件内核对象hEvent,它处于未通知状态。线程T1、T2、T3内部调用“WaitForSingleObject(hEvent, INFINITE);”,这样当该事件内核对象变为“已通知”状态的话,T1线程“可能”被唤醒,但是其他的线程T2和T3呢?由于在T1线程内部WaitForSingleObject函数返回成功,又将hEvent事件内核对象设置为“未通知”状态,那么T2和T3就不可能被唤醒。

  也就是说,“成功等待的副作用”会导致多个等待在同一个内核对象上的线程只能被唤醒一个

 

  好,下面我们来讨论“事件内核对象”。

  在所有内核对象中,事件内核对象是最基本的一个内核对象。在事件内核对象内部,有以下几个比较重要的数据:

1、有一个“引用计数”:指明被打开的次数;

2、一个“布尔值”:指明该事件内核对象是自动重置的还是人工重置的;

3、另一个“布尔值”:指明该事件内核对象是“已通知状态”还是“未通知状态”。

  事件内核对象可以通知一个事件已经完成。有两种不同的类型:自动重置和人工重置。当人工重置的事件内核对象得到通知的时候,所有等待在事件内核对象上的线程都变成可调度线程。当一个自动重置的事件内核对象得到通知的时候,等待在该事件内核对象上的线程只有一个能变成可调度状态。

 

  要使用事件内核对象,首先调用CreateEvent函数来创建一个事件内核对象:

HANDLE CreateEvent(
   PSECURITY_ATTRIBUTES psa,
   BOOL bManualReset,
   BOOL bInitialState,
   PCTSTR pszName);

 

  参数psa是一个SECURITY_ATTRIBUTES(安全属性)结构的指针,一般设置为默认安全,传递NULL。

  bManualReset参数指定了该内核对象是人工重置(传递TRUE)的还是自动重置(传递FALSE)的。

  bInitialState参数指定了该内核对象起始状态是已通知(传递TRUE)还是未通知状态(FALSE)。

  pszName参数为要创建的事件内核对象起一个名字,如果传递NULL,则创建一个“匿名”的事件内核对象。如果不传递NULL,且系统中已经存在该名字的事件内核对象,则不创建新的事件内核对象而是打开这个已经存在的,返回它的句柄。

  该函数如果成功,返回事件内核对象的句柄,这样就可以操纵它了。如果失败,返回NULL。

 

  Windows Vista提供了另一个函数来创建事件内核对象:

HANDLE CreateEventEx(
   PSECURITY_ATTRIBUTES psa,
   PCTSTR pszName,
   DWORD dwFlags,
   DWORD dwDesiredAccess);

 

  该函数的psa和pszName参数的意义和函数CreateEvent相同。

  参数dwFlags可以有以下数据的“位或组合”:

WinBase.h中定义的位组合数据 

描述 

CREATE_EVENT_INITIAL_SET (0x00000002)

如果设置了该数据,则表明事件内核对象的起始状态为已通知状态;否则起始状态为未通知状态。

CREATE_EVENT_MANUAL_RESET (0x00000001)

如果设置了该数据,则表明事件内核对象是人工重置的;否则为自动重置的。

  参数dwDesiredAccess可以让你对该事件内核对象的访问加一些限制,本书没有细说,查MSDN就可以了吧。

 

  可以打开一个“命名”的事件内核对象:

HANDLE OpenEvent(
   DWORD dwDesiredAccess,
   BOOL bInherit,
   PCTSTR pszName);

 

  第一个参数指明的访问的限制,第二个参数表示该事件内核对象的句柄能够被子进程继承,第三个参数指明了该事件内核对象的名字。该函数成功返回事件内核对象的句柄,失败返回NULL。

  当不需要使用这些句柄时,需要调用CloseHandle函数来递减内核对象的引用计数,使得该内核对象可以被及时清除。

 

  当一个事件内核对象被创建之后,你可以直接控制它的状态。你可以通知它,使得它从未通知状态转变为已通知状态: 

BOOL SetEvent(HANDLE hEvent);

 

  也可以重新设置它,使它从已通知状态变为未通知状态:

BOOL ResetEvent(HANDLE hEvent);

 

  一个自动重置的事件内核对象,如果等待成功,由于“成功等待的副作用”机制会将该事件内核对象由已通知状态变为未通知状态,这个时候就没有必要调用ResetEvent函数了。

  如果是一个人工重置的事件内核对象,等待成功之后,并不会被设置为未通知状态,而是要程序员调用ResetRvent函数来使之转变为未通知状态。

 

  还有要注意的就是,一个“自动重置”的事件内核对象收到通知,转变为已通知状态的时候,最多只能唤醒“一个”等待在它上的线程。一个“人工重置”的事件内核对象收到通知,转变为已通知状态的时候,能够唤醒“所有”等待在它上的线程。

posted on 2008-08-11 13:40  小虎无忧  阅读(950)  评论(0编辑  收藏  举报

导航