当进程/线程正在运行的时候,进程内核对象处于未通知状态,当进程终止运行的时候,它就变
为已通知状态。进程内核对象中是个布尔值,当对象创建时,该值被初始化为FA L S E(未通知
状态)。当进程终止运行时,操作系统自动将对应的对象布尔值改为T R U E,表示该对象已经得
到通知。
eg: WaitForSingleObject等待一个进程/线程结束,等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止
可以以两种不同的方式来使用Wa i t F o r M u l t i p l e O b j e c t s函数。一种方式是让线程进入等待状态,直到指定内核对象中的任何一个变为已通知状态。另一种方式是让线程进入等待状态,直
到所有指定的内核对象都变为已通知状态。
Wa i t F o r M u l t i p l e O b j e c t s函数的返回值告诉调用线程,为什么它会被重新调度。可能的返回值
是WA I T FA I L E D和WA I T T I M E O U T,这两个值的作用是很清楚的。如果为f Wa i tAl l参数传递
T R U E,同时所有对象均变为已通知状态,那么返回值是WA I T O B J E C T 0。如果为f Wa i t A l l传递
FA L S E,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知
道哪个对象变为已通知状态。返回值是WA I T O B J E C T 0与(WA I T O B J E C T 0 + d w C o u n t - 1)之
间的一个值。换句话说,如果返回值不是WA I T T I M E O U T,也不是WA I T FA I L E D,那么应该从
返回值中减去WA I T O B J E C T 0。产生的数字是作为第二个参数传递给Wa i t F o r M u l t i p l e O b j e c t s的
句柄数组中的索引。该索引说明哪个对象变为已通知状态
成功等待的副作用
对于有些内核对象来说,成功地调用Wa i t F o r S i n g l e O b j e c t和Wa i t F o r M u l t i p l e O b j e c t s,实际
上会改变对象的状态。成功地调用是指函数发现对象已经得到通知并且返回一个相对于
WA I T O B J E C T 0的值。如果函数返回WA I T T I M E O U T或WA I T FA I L E D,那么调用就没有成
功。如果函数调用没有成功,对象的状态就不可能改变。
当一个对象的状态改变时,我称之为成功等待的副作用。例如,有一个线程正在等待自动
清除事件对象(本章后面将要介绍)。当事件对象变为已通知状态时,函数就会发现这个情况,
并将WA I T O B J E C T 0返回给调用线程。但是就在函数返回之前,该事件将被置为未通知状态,
这就是成功等待的副作用。
这个副作用将用于自动清除内核对象,因为它是M i c r o s o f t为这种类型的对象定义的规则之
一。其他对象拥有不同的副作用,而有些对象则根本没有任何副作用。进程和线程内核对象就
根本没有任何副作用,也就是说,在这些对象之一上进行等待决不会改变对象的状态。由于本
章要介绍各种不同的内核对象,因此我们将要详细说明它们的成功等待的副作用。
究竟是什么原因使得Wa i t F o r M u l t i p l e O b j e c t s函数如此有用呢,因为它能够以原子操作方式
来执行它的所有操作。当一个线程调用Wa i t F o r M u l t i p l e O b j e c t s函数时,该函数能够测试所有对
象的通知状态,并且能够将所有必要的副作用作为一项操作来执行。
让我们观察一个例子。两个线程以完全相同的方式来调用Wa i t F o r M u l t i p l e O b j e c t s:
当Wa i t F o r M u l t i p l e O b j e c t s函数被调用时,两个事件都处于未通知状态,这就迫使两个线程
都进入等待状态。然后h A u t o R e s e t E v e n t 1对象变为已通知状态。两个线程都发现,该事件已经
变为已通知状态,但是它们都无法被唤醒,因为h A u t o R e s e t E v e n t 2仍然处于未通知状态。由于
两个线程都没有等待成功,因此没有对h A u t o R e s e t E v e n t 1对象产生任何副作用。
接着,h A u t o R e s e t E v e n t 2变为已通知状态。这时,两个线程中的一个发现,两个对象都变
为已通知状态。等待取得了成功,两个事件对象均被置为未通知状态,该线程变为可调度的线
程。但是另一个线程的情况如何呢?它将继续等待,直到它发现两个事件对象都处于已通知状
态。尽管它原先发现h A u t o R e s e t E v e n t 1处于已通知状态,但是现在它将该对象视为未通知状
态。
前面讲过,有一个重要问题必须注意,即Wa i t F o r M u l t i p l e O b j e c t s是以原子操作方式运行的。
当它检查内核对象的状态时,其他任何线程都无法背着对象改变它的状态。这可以防止出现死
锁情况。试想,如果一个线程看到h A u t o R e s e t E v e n t 1已经得到通知并将事件重置为未通知状态,
然后,另一个线程发现h A u t o R e s e t E v e n t 2已经得到通知并将该事件重置为未通知状态,那么这
两个线程均将被冻结:一个线程将等待另一个线程已经得到的对象,另一个线程将等待该线程
已经得到的对象。Wa i t F o r M u l t i p l e O b j e c t s能够确保这种情况永远不会发生。
事件内核对象事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,
另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调
度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线
程。
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES ,
BOOL ,
BOOL ,
LPTSTR
); - bInitialState
- [in] Boolean that specifies the initial state of the event object. If TRUE,
the initial state is signaled; otherwise, it is nonsignaled.
M i c r o s o f t为自动重置的
事件定义了应该成功等待的副作用规则,即
当线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。这就是自动重置的事件如何获得它们的
名字的方法。通常没有必要为自动重置的事件调用R e s e t E v e n t函数,因为系统会自动对事件进
行重置。但是, M i c r o s o f t没有为人工重置的事件定义成功等待的副作用。
//手动事件,初始无信号
CreateEvent(NULL, TRUE, FALSE, NULL);
// 自动事件
CreateEvent(NULL, FALSE, FALSE, NULL);
等待定时器内核对象HANDLE WINAPI CreateWaitableTimer(
__in LPSECURITY_ATTRIBUTES
,
__in BOOL
,
__in LPCTSTR
);
当发出人工重置的定时器信号通知时,等待该定时器的所有线程均变为可调度线程。当发出自
动重置的定时器信号通知时,只有一个等待的线程变为可调度线程。
等待定时器对象总是在未通知状态中创建。必须调用S e t Wa i t a b l e Ti m e r函数来告诉定时器
你想在何时让它成为已通知状态
BOOL WINAPI SetWaitableTimer(
__in HANDLE
,
__in const LARGE_INTEGER*
,
__in LONG
,
__in PTIMERAPCROUTINE
,
__in LPVOID
,
__in BOOL
);
显然, h Ti m e r参数用于指明你要设置的定
时器。p D u e Ti m e和l P e r i o d两个参数是一道使用的。P D u e Ti m e r参数用于指明定时器何时应该第
一次报时,而l P e r i o d参数则用于指明此后定时器应该间隔多长时间报时一次。
信标内核对象信标内核对象用于对资源进行计数。它们与所有内核对象一样,包含一个使用数量,但是
它们也包含另外两个带符号的3 2位值,一个是最大资源数量,一个是当前资源数量。最大资源
数量用于标识信标能够控制的资源的最大数量,而当前资源数量则用于标识当前可以使用的资
源的数量。
信标的使用规则如下:
• 如果当前资源的数量大于0,则发出信标信号。
• 如果当前资源数量是0,则不发出信标信号。
• 系统决不允许当前资源的数量为负值。
• 当前资源数量决不能大于最大资源数量。
当使用信标时,不要将信标对象的使用数量与它的当前资源数量混为一谈。
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES ,
LONG ,
LONG ,
LPCTSTR
);
由于当前资源数量被初始化为0,因此不发出信标信号。等待信标的所有线程均进入等待状态
通过调用等待函数,传递负责保护资源的信标的句柄,线程就能够获得对该资源的访问权。
从内部来说,
该等待函数要检查信标的当前资源数量,如果它的值大于0(信标已经发出信号),那么计数器递减1,调用线程保持可调度状态。信标的出色之处在于它们能够以原子操作方式
来执行测试和设置操作,这就是说,当向信标申请一个资源时,操作系统就要检查是否有这个
资源可供使用,同时将可用资源的数量递减,而不让另一个线程加以干扰。只有当资源数量递
减后,系统才允许另一个线程申请对资源的访问权。
如果该等待函数确定信标的当前资源数量是0(信标没有发出通知信号),那么系统就调用
函数进入等待状态。当另一个线程将对信标的当前资源数量进行递增时,系统会记住该等待线
程(或多个线程),并允许它变为可调度状态(相应地递减它的当前资源数量)。
通过调用R e l e a s e S e m a p h o r e函数,线程就能够对信标的当前资源数量进行递增
互斥对象内核对象
互斥对象(m u t e x)内核对象能够确保线程拥有对单个资源的互斥访问权。实际上互斥对
象是因此而得名的。互斥对象包含一个使用数量,一个线程I D和一个递归计数器。互斥对象的
行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。
这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够
访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。
I D用于标识系统中的哪个线程当前拥有互斥对象,递归计数器用于指明该线程拥有互斥对
象的次数。互斥对象有许多用途,属于最常用的内核对象之一。通常来说,它们用于保护由多
个线程访问的内存块。如果多个线程要同时访问内存块,内存块中的数据就可能遭到破坏。互
斥对象能够保证访问内存块的任何线程拥有对该内存块的独占访问权,这样就能够保证数据的
完整性。
互斥对象的使用规则如下:
• 如果线程I D是0(这是个无效I D),互斥对象不被任何线程所拥有,并且发出该互斥对象
的通知信号。
• 如果I D是个非0数字,那么一个线程就拥有互斥对象,并且不发出该互斥对象的通知信
号。
• 与所有其他内核对象不同, 互斥对象在操作系统中拥有特殊的代码,允许它们违反正常
的规则
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES ,
BOOL ,
LPCTSTR
);
- bInitialOwner
- [in] Boolean that specifies the initial owner of the mutex object. If this
value is TRUE and the caller created the mutex, the calling thread obtains
ownership of the mutex object. Otherwise, the calling thread does not obtain
ownership of the mutex. To determine if the caller created the mutex, see the
Return Values section.
- f I n i t i a l O w n e r参数用于控制互斥对象的初始状态。如果传递FA L S E(这是通常情况下传递
的值),那么互斥对象的I D和递归计数器均被设置为0。这意味着该互斥对象没有被任何线程所
拥有,因此要发出它的通知信号。
如果为f I n i t i a l O w n e r参数传递T R U E,那么该对象的线程I D被设置为调用线程的I D,递归
计数器被设置为1。由于I D是个非0数字,因此该互斥对象开始时不发出通知信号。 - 通过调用一个等待函数,并传递负责保护资源的互斥对象的句柄,线程就能够获得对共享
资源的访问权。在内部,等待函数要检查线程的I D,以了解它是否是0(互斥对象发出通知信
号)。如果线程I D是0,那么该线程I D被设置为调用线程的I D,递归计数器被设置为1,同时,
调用线程保持可调度状态。 - 对于互斥对象来说,正常的内核对象的已通知和未通知规则存在一个特殊的异常情况。比
如说,一个线程试图等待一个未通知的互斥对象。在这种情况下,该线程通常被置于等待状态。
然而,系统要查看试图获取互斥对象的线程的I D是否与互斥对象中记录的线程I D相同。如果两
个线程I D相同,即使互斥对象处于未通知状态,系统也允许该线程保持可调度状态。我们不认
为该“异常”行为特性适用于系统中的任何地方的其他内核对象。每当线程成功地等待互斥对
象时,该对象的递归计数器就递增。若要使递归计数器的值大于1,唯一的方法是线程多次等
待相同的互斥对象,以便利用这个异常规则。一旦线程成功地等待到一个互斥对象,该线程就知道它已经拥有对受保护资源的独占访问
权。试图访问该资源的任何其他线程(通过等待相同的互斥对象)均被置于等待状态中。当目
前拥有对资源的访问权的线程不再需要它的访问权时,它必须调用R e l e a s e M u t e x函数来释放该
互斥对象该函数将对象的递归计数器递减1。如果线程多次成功地等待一个互斥对象,在互斥对象
的递归计数器变成0之前,该线程必须以同样的次数调用R e l e a s e M u t e x函数。当递归计数器到
达0时,该线程I D也被置为0,同时该对象变为已通知状态。当该对象变为已通知状态时,系统要查看是否有任何线程正在等待互斥对象。如果有,系
统将“按公平原则”选定等待线程中的一个,为它赋予互斥对象的所有权。当然,这意味着线
程I D被设置为选定的线程的I D,并且递归计数器被置为1。如果没有其他线程正在等待互斥对
象,那么该互斥对象将保持已通知状态,这样,等待互斥对象的下一个线程就立即可以得到互
斥对象。 - thread1 :
createmutex mutex
waitsingleobject(mutex)
waitsingleobject(mutex)//计数加1
releaseMutex对应--1
表9-1 互斥对象与关键代码段的比较
特性互斥对象关键代码段
运行速度慢快
是否能够跨进程是否
边界来使用
声明HANDLE hmtx; CRITICAL_SECTION cs;
初始化h m t x = C r e a t e M u t e x 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 );
(N U L L,FA L S E,N U L L);
清除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 E n t e r C r i t i c a l S e c t i o n(& c s);
(h m t x , I N F I N I T E);
0等待Wa i t F o r S i n g l e O b j e c t Tr y E n t e r C r i t i c a l S e c t i o n(& c s);
(h m t x , 0);
任意等待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或类似的函数)
9.6.3 Queue
阅读(1603) | 评论(0) | 转发(0) |