1、已通知和未通知状态
■ 进程 | ■ 文件修改通知 |
■ 线程 | ■ 事件 |
■ 作业 | ■ 可等待定时器 |
■ 文件 | ■ 信标 |
■ 控制台输入 | ■ 互斥对象 |
2、等待函数
- WaitForSingleObject()
- WaitForMultipleObjects()
3、成功等待的副作用
- 当一个对象的状态改变时,我称之为成功等待的副作用。
- 如果一个线程等待一个对象,然后该线程暂停运行,那么系统就会忘记该线程正在等待该对象。这是一个特性,因为没有理由为一个暂停运行的线程进行调度。当后来该线程恢复运行时,系统将认为该线程刚刚开始等待该对象。
4、事件内核对象
- 人工重置事件 and 自动重置事件
- 当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
- Microsoft为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。这就是自动重置的事件如何获得它们的名字的方法。通常没有必要为自动重置的事件调用ResetEvent函数,因为系统会自动对事件进行重置。但是, Microsoft没有为人工重置的事件定义成功等待的副作用。
- CreateEvent(), SetEvent(), ResetEvent(), PulseEvent()
- SetEvent可以将事件改为已通知状态
- ResetEvent可以将该事件改为未通知状态
5、等待定时器内核对象
- CreateWaitableTimer(), OpenWaitableTimer()
- 当发出人工重置的定时器信号通知时,等待该定时器的所有线程均变为可调度线程。当发出自动重置的定时器信号通知时,只有一个等待的线程变为可调度线程。
- SetWaitableTimer()
- APC例程可以在定时器报时的时候由调用SetWaitableTimer函数的同一个线程来调用,但是只有在调用线程处于待命状态下才能调用。换句话说,该线程必须正在SleepEx , WaitForSingleObjectEx,WaitForMultipleObjectsEx,MsgWaitForMultipleObjectsEx或SingleObjectAndWait等函数的调用中等待。如果该线程不在这些函数中的某个函数中等待,系统将不给定时器A P C例程排队。这可以防止线程的APC队列中塞满定时器A P C通知,这会浪费系统中的大量内存。
- 最后要说明的是,线程不应该等待定时器的句柄,也不应该以待命的方式等待定时器。
- 通常没有理由使用带有等待定时器的APC例程,因为你始终都可以等待定时器变为已通知状态,然后做你想要做的事情。
- 凡是称职的Windows程序员都会立即将等待定时器与用户定时器(用SetTimer函数进行设置)进行比较。它们之间的最大差别是,用户定时器需要在应用程序中设置许多附加的用户界面结构,这使定时器变得资源更加密集。另外,等待定时器属于内核对象,这意味着它们可以供多个线程共享,并且是安全的。
- 当用户定时器报时的时候,只有一个线程得到通知。另一方面,多个线程可以在等待定时器上进行等待,如果定时器是个人工重置的定时器,则可以调度若干个线程。
6、信号量内核对象
- CreateSemaphore(), OpenSemaphore(), ReleaseSemaphore()
- 有时,有必要知道信标的当前资源数量而不修改这个数量,但是没有一个函数可以用来查询信标的当前资源数量的值。
- ReleaseSemaphore函数增加当前资源数量,WaitForSingleObject or WaitForMultipleObjects消费当前资源
7、互斥对象内核对象
- CreateMutex(), OpenMutex(), ReleaseMutex()
- 互斥对象的行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。
- 对于互斥对象来说,正常的内核对象的已通知和未通知规则存在一个特殊的异常情况。比如说,一个线程试图等待一个未通知的互斥对象。在这种情况下,该线程通常被置于等待状态。然而,系统要查看试图获取互斥对象的线程的I D是否与互斥对象中记录的线程I D相同。如果两个线程I D相同,即使互斥对象处于未通知状态,系统也允许该线程保持可调度状态。我们不认为该“异常”行为特性适用于系统中的任何地方的其他内核对象。每当线程成功地等待互斥对象时,该对象的递归计数器就递增。若要使递归计数器的值大于1,唯一的方法是线程多次等待相同的互斥对象,以便利用这个异常规则。
- 互斥对象的线程所有权概念是互斥对象为什么会拥有特殊异常规则的原因,这个异常规则使得线程能够获取该互斥对象,尽管它没有发出通知。
- 就等待线程的调度而言,互斥对象与关键代码段之间有着相同的特性。但是它们在其他属性方面却各不相同。下表对它们进行了各方面的比较:
特性 互斥对象 关键代码段 运行速度 慢 快 是否能够跨进程边界来使用 是 否 声明 HANDLE hmtx; CRITICAL_SECTION cs; 初始化 h m t x = C r e a t e M u t e x(N U L L,FA L S E,N U L L); I n i t i a l i z e C r i t i c a l S e c t i o n ( & e s ); 清除 C l o s e H a n d l e(h m t x); D e l e t e C r i t i c a l S e c t i o n(& c s); 无限等待 Wa i t F o r S i n g l e O b j e c t(h m t x , I N F I N I T E); E n t e r C r i t i c a l S e c t i o n(& c s); 0等待 Wa i t F o r S i n g l e O b j e c t Tr y(h m t x , 0); E n t e r C r i t i c a l S e c t i o n(& c s); 任意等待 Wa i t F o r S i n g l e O b j e c t(h m t x , d w M i l l i s e c o n d s); 不能 释放 R e l e a s e M u t e x(h m t x); L e a v e C r i t i c a l S e c t i o n(& c s); 是否能够等待其他内核对象 是(使用Wa i t F o r M u l t i p l e O b j e c t s或类似的函数) 否
8、其他线程同步函数
- 设备对象是可以同步的内核对象,这意味着可以调用WaitForSingleObject函数,传递文件、套接字和通信端口的句柄。当系统执行异步I / O时,设备对象处于未通知状态。一旦操作完成,系统就将对象的状态改为已通知状态,这样,该线程就知道操作已经完成。此时,该线程就可以继续运行。
- DWORD WaitForInputIdle(HANDLE hProcess, DWORD dwMilliseconds);该函数将一直处于等待状态,直到hProcess标识的进程在创建应用程序的第一个窗口的线程中已经没有尚未处理的输入为止。
- WaitForInputIdle函数可以用于父进程。父进程产生子进程,以便执行某些操作。当父进程的线程调用CreateProcess时,该父进程的线程将在子进程初始化时继续运行。父进程的线程可能需要获得子进程创建的窗口的句柄。如果父进程的线程想要知道子进程何时完成初始化,唯一的办法是等待,直到子进程不再处理任何输入为止。因此,当调用CreateProcess后,父进程的线程就调用WaitForInputIdle。
- 当需要将击键输入纳入应用程序时,也可以调用WaitForInputIdle,WaitForInputIdle将导致应用程序处于等待状态,直到对话框创建完成并准备接受用户的输入。这时,该应用程序可以将其他的击键输入纳入对话框及其控件,使它能够继续执行它需要的操作。
- MsgWaitForMultipleObjects and MsgWaitForMultipleObjectsEx 函数与WaitForSingleObject函数十分相似。差别在于它们允许线程在内核对象变成已通知状态或窗口消息需要调度到调用线程创建的窗口中时被调度。
- WaitForDebugEvent函数
- DWORD SingleObjectAndWait(
HANDLE hObjectToSignal,
HANDLE hObjectToWaitOn,
DWORD dwMilliseconds,
BOOL fAlertable);
函数用于在单个原子方式的操作中发出关于内核对象的通知并等待另一个内核对象。
hObjectToSignal参数必须标识一个互斥对象、信标对象或事件。任何其他类型的对象将导致该函数返回WAIT_FAILED.在内部,该函数将观察对象的类型,并分别运行ReleaseMutex、ReleaseSemaphore (其数量为1) 或ResetEvent中的相应参数。
hObjectToWaitOn参数用于标识下列任何一个内核对象:互斥对象、信标、事件、定时器、进程、线程、作业、控制台输入和修改通知。
9、内核对象与线程同步之间的相互关系
对象 | 何时处于未通知状态 | 何时处于已通知状态 | 成功等待的副作用 |
进程 | 当进程仍然活动时 | 当进程终止运行时(E x i t P r o c e s s,Te r m i n a t e P r o c e s s) | 无 |
线程 | 当线程仍然活动时 | 当线程终止运行时(E x i t T h r e a d,Te r m i n a t e T h r e a d) | 无 |
作业 | 当作业的时间尚未结束时 | 当作业的时间已经结束时 | 无 |
文件 | 当I / O请求正在处理时 | 当I / O请求处理完毕时 | 无 |
控制台输入 | 不存在任何输入 | 当存在输入时 | 无 |
文件修改通知 | 没有任何文件被修改 | 当文件系统发现修改时 | 重置通知 |
自动重置事件 | R e s e t E v e n t , P u l s e - E v e n t或等待成功 | 当调用S e t E v e n t / P u l s e E v e n t时 | 重置事件 |
人工重置事件 | R e s e t E v e n t或P u l s e E v e n t | 当调用S e t E v e n t / P u l s e E v e n t时 | 无 |
自动重置等待定时器 | C a n c e l Wa i t a b l e Ti m e r或等待成功 | 当时间到时(S e t Wa i t a b l e Ti m e r) | 重置定时器 |
人工重置等待定时器 | C a n c e l Wa i t a b l e Ti m e r | 当时间到时(S e t Wa i t a b l e Ti m e r) | 无 |
信标 | 等待成功 | 当数量> 0时(R e l e a s e S e m a p h o r e) | 数量递减1 |
互斥对象 | 等待成功 | 当未被线程拥有时(R e l e a s e互斥对象) | 将所有权赋予线程 |
关键代码段(用户方式) | 等待成功((Tr y)E n t e r C r i t i c a l S e c t i o n) | 当未被线程拥有时(L e a v e C r i t i c a l S e c t i o n) | 将所有权赋予线程 |
互锁(用户方式)函数决不会导致线程变为非调度状态,它们会改变一个值并立即返回。