Windows编程-- 线程和内核对象的同步 - 互斥对象内核对象

互斥对象(mutex内核对象能够确保线程拥有对单个资源的互斥访问权

互斥对象包含一个使用数量,一个线程ID和一个递归计数器

互斥对象的行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。

这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值

ID用于标识系统中的哪个线程当前拥有互斥对象

递归计数器用于指明该线程拥有互斥对 象的次数

作用:

通常来说,它们用于保护由多个线程访问的内存块。如果多个线程要同时访问内存块,内存块中的数据就可能遭到破坏。互斥对象能够保证访问内存块的任何线程拥有对该内存块的独占访问权,这样就能够保证数据的完整性。

互斥对象的使用规则如下:

• 如果线程ID是0(这是个无效ID),互斥对象不被任何线程所拥有,并且发出该互斥对象的通知信号。   

• 如果I D是个非0数字,那么一个线程就拥有互斥对象,并且不发出该互斥对象的通知信号。

• 与所有其他内核对象不同, 互斥对象在操作系统中拥有特殊的代码,允许它们违反正常的规则。

若要使用互斥对象,必须有一个进程首先调用CreateMutex,以便创建互斥对象:

HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOLfInitialOwner,
PCTSTRpszName);

psa和pszName参数是内核对象的安全属性和内核对象的命名,

fInitialOwner参数用于控制互斥对象的初始状态

如果传递FALSE这是通常情况下传递的值),那么互斥对象的ID和递归计数器均被设置为0。这意味着该互斥对象没有被任何线程所拥有,因此要发出它的通知信号

如果传递TRUE,那么该对象的线程ID被设置为调用线程的ID,递归计数器被设置为1。由于ID是个非0数字,因此该互斥对象开始时不发出通知信号

通过调用OpenMutex,另一个进程可以获得它自己进程与现有互斥对象相关的句柄:

HANDLE OpenMutex(
DWORDfdwAccess,
BOOLbInheritHandle,
PCTSTRpszName);

通过调用一个等待函数,并传递负责保护资源的互斥对象的句柄,线程就能够获得对共享资源的访问权。在内部,等待函数要检查线程的ID,以了解它是否是0互斥对象发出通知信号)。

如果线程ID是0,那么该线程ID被设置为调用线程的ID,递归计数器被设置为1,同时,调用线程保持可调度状态。

如果等待函数发现ID不是0不发出互斥对象的通知信号),那么调用线程便进入等待状态。系统将记住这个情况,并且在互斥对象的I D重新设置为0时,将线程ID设置为等待线程的I D,将递归计数器设置为1,并且允许等待线程再次成为可调度线程。与所有情况一样,对互斥内核对象进行的检查和修改都是以原子操作方式进行的。

异常规则

对于互斥对象来说,正常的内核对象的已通知和未通知规则存在一个特殊的异常情况

比如说,一个线程试图等待一个未通知的互斥对象。在这种情况下,该线程通常被置于等待状态。然而,系统要查看试图获取互斥对象的线程的I D是否与互斥对象中记录的线程I D相同如果两个线程I D相同,即使互斥对象处于未通知状态,系统也允许该线程保持可调度状态。我们不认为该“异常”行为特性适用于系统中的任何地方的其他内核对象。每当线程成功地等待互斥对象时,该对象的递归计数器就递增。若要使递归计数器的值大于1,唯一的方法是线程多次等待相同的互斥对象,以便利用这个异常规则。

一旦线程成功地等待到一个互斥对象,该线程就知道它已经拥有对受保护资源的独占访问权。试图访问该资源的任何其他线程(通过等待相同的互斥对象)均被置于等待状态中。当目前拥有对资源的访问权的线程不再需要它的访问权时,它必须调用ReleaseMutex函数来释放该互斥对象:

 

BOOL ReleaseMutex(HANDLE hMutex);


该函数将对象的递归计数器递减1。如果线程多次成功地等待一个互斥对象,在互斥对象的递归计数器变成0之前,该线程必须以同样的次数调用ReleaseMutex函数。当递归计数器到达0时,该线程ID也被置为0,同时该对象变为已通知状态。

当该对象变为已通知状态时,系统要查看是否有任何线程正在等待互斥对象。如果有,系统将“按公平原则”选定等待线程中的一个,为它赋予互斥对象的所有权。当然,这意味着线程I D被设置为选定的线程的ID,并且递归计数器被置为1。如果没有其他线程正在等待互斥对象,那么该互斥对象将保持已通知状态,这样,等待互斥对象的下一个线程就立即可以得到互斥对象。

释放问题

互斥对象不同于所有其他内核对象,因为互斥对象有一个“线程所有权”的概念。本章介绍的其他内核对象中,没有一种对象能够记住哪个线程成功地等待到该对象,只有互斥对象能够对此保持跟踪。互斥对象的线程所有权概念是互斥对象为什么会拥有特殊异常规则的原因,这个异常规则使得线程能够获取该互斥对象,尽管它没有发出通知。

这个异常规则不仅适用于试图获取互斥对象的线程,而且适用于试图释放互斥对象的线程。当一个线程调用ReleaseMutex函数时,该函数要查看调用线程的ID是否与互斥对象中的线程ID相匹配。如果两个ID相匹配,递归计数器就会像前面介绍的那样递减。如果两个线程的ID不匹配,那么ReleaseMutex函数将不进行任何操作,而是将FA LSE(表示失败)返回给调用者。此时调用GetLastError,将返回ERROR_NOT_OWNER(试图释放不是调用者拥有的互斥对象)。

因此,如果在释放互斥对象之前,拥有互斥对象的线程终止运行(使用ExitThread、TerminateThread、ExitProcess或TerminateProcess函数),那么互斥对象和正在等待互斥对象的其他线程将会发生什么情况呢?答案是,系统将把该互斥对象视为已经被放弃——拥有互斥对象的线程决不会释放它,因为该线程已经终止运行。

由于系统保持对所有互斥对象和线程内核对象的跟踪,因此它能准确的知道互斥对象何时被放弃。当一个互斥对象被放弃时,系统将自动把互斥对象的I D复置为0,并将它的递归计数器复置为0。然后,系统要查看目前是否有任何线程正在等待该互斥对象。如果有,系统将“公平地”选定一个等待线程,将I D设置为选定的线程的I D,并将递归计数器设置为1,同时,选定的线程变为可调度线程。

这与前面的情况相同,差别在于等待函数并不将通常的WA I T _ OBJECT_0值返回给线程。相反,等待函数返回的是特殊的WAIT_ABANDONED值。这个特殊的返回值(它只适用于互斥对象)用于指明线程正在等待的互斥对象是由另一个线程拥有的而这另一个线程已经在它完成对共享资源的使用前终止运行。(FangSH注: 就像被遗弃。有的等待,有的中途放弃。结果都没有被用到)这不是可以进入的最佳情况。新调度的线程不知道目前资源处于何种状态,也许该资源已经完全被破坏了。在这种情况下必须自己决定应用程序应该怎么办。

在实际运行环境中,大多数应用程序从不明确检查WAIT_ABANDONED返回值,因为线程很少是刚刚终止运行(上面介绍的情况提供了另一个例子,说明为什么决不应该调用TerminateThread函数)。

互斥对象与关键代码段的比较

就等待线程的调度而言,互斥对象与关键代码段之间有着相同的特性。但是它们在其他属性方面却各不相同。表对它们进行了各方面的比较。

表9-1 互斥对象与关键代码段的比较

特性

互斥对象

关键代码段

运行速度

是否能够跨进程边界来使用

声明

HANDLE hmtx;

CRITICAL_SECTION cs;

初始化

hmtx = CreateMutex(NULL,FALSE,NULL);

InitializeCriticalSection(&es );

清除

CloseHandle(hmtx);

DeleteCriticalSection(&cs);

无限等待

WaitForSingleObject(hmtx , INFINITE);

EnterCriticalSection(&cs);

0等待

WaitForSingleObjectTry(hmtx , 0);

EnterCriticalSection(&cs);

任意等待

WaitForSingleObject(hmtx , dw Milliseconds);

不能

释放

ReleaseMutex(hmtx);

LeaveCriticalSection(&cs);

是否能够等待其他内核对象

是(使用WaitForMultipleObjec ts或类似的函数)

FangSH  2011-01-04

原文地址:https://www.cnblogs.com/fangshenghui/p/1926791.html