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

 

互斥对象的组成

一个使用数量

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

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

 

互斥对象的使用规则如下

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

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

与所有其他内核对象不同, 互斥对象在操作系统中拥有特殊的代码,允许它们违反正常的规则(后面将要介绍这个异常情况)。

 

互斥对象作用

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

 

创建互斥对象

HANDLE CreateMutex(

   PSECURITY_ATTRIBUTES psa,

   BOOL fInitialOwner,

   PCTSTR pszName);

psapszName这两个参数(见内核对象的安全性  跨越进程边界共享内核对象【命名对象】

fInitialOwner参数用于控制互斥对象的初始状态。如果传递FALSE(这是通常情况下传递的值),那么互斥对象的I D和递归计数器均被设置为0。这意味着该互斥对象没有被任何线程所拥有,因此要发出它的通知信号。

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

 

打开互斥对象

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

HANDLE OpenMutex(

   DWORD fdwAccess,

   BOOL bInheritHandle,

   PCTSTR pszName);

通过调用一个等待函数,并传递负责保护资源的互斥对象的句柄,线程就能够获得对共享资源的访问权。在内部,等待函数要检查线程的ID,以了解它是否是0(互斥对象发出通知信号)。如果线程ID0,那么该线程ID被设置为调用线程的I D,递归计数器被设置为1,同时,调用线程保持可调度状态。

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

 

特殊的异常情况

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

互斥对象释放问题

当目前拥有对资源的访问权的线程不再需要它的访问权时,它必须调用ReleaseMutex函数来释放该互斥对象:

BOOL ReleaseMutex(HANDLE hMutex);

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

 

 

互斥对象有一个线程所有权的概念。互斥对象的线程所有权概念是互斥对象为什么会拥有特殊异常规则的原因。

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

因此,如果在释放互斥对象之前,拥有互斥对象的线程终止运行(使ExitThreadTerminateThreadExitProcessTerminateProcess函数),那么互斥对象和正在等待互斥对象的其他线程将会发生什么情况呢?答案是,系统将把该互斥对象视为已经被放弃——拥有互斥对象的线程决不会释放它,因为该线程已经终止运行。(上面介绍的情况提供了另一个例子,说明为什么决不应该调用TerminateThread函数)。

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

 

互斥对象和关键代码段的异同

互斥对象的行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。

 

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

特性

互斥对象

关键代码段

运行速度

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

声明

HANDLE hmtx

CRITICAL_SECTION cs

初始化

hmtx = CreateMutexNULLFALSENULL);

InitializeCriticalSe ction(&es)

清除

CloseHandlehmtx);

DeleteCriticalSectio n&cs);

无限等待

WaitForSingleObjecthmtx ,IN FINITE);

EnterCriticalSection&cs);

0等待

WaitForSingleObjectTryhmtx , 0);

EnterCriticalSection& c s);

任意等待

WaitForSingleObjecthmtx,dwMilliseconds);

不能

释放

ReleaseMutexhmtx);

LeaveCriticalSectio n&cs);

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

是(使用WaitForMultipleObjects或类似的函数)

 例子

vs2008代码下载

// -------------------------------------------------------------------------
// 文件名 : 09_CreateMutex.cpp
// 创建者 : 方煜宽
// 创建时间 : 2010-8-11 1:25
// 功能描述 : 互斥对象内核对象
//
// -------------------------------------------------------------------------
#include "stdafx.h"
#include
<windows.h>
#include
<iostream>
using namespace std;

int g_nCount = 0;
HANDLE g_hMutex;

DWORD WINAPI Thread1(LPVOID pParam)
{
while(true)
{
if (g_nCount > 99)
break;

WaitForSingleObject(g_hMutex, INFINITE);
Sleep(
1);
cout
<<"Thread1:" << g_nCount++ << endl;
ReleaseMutex(g_hMutex);
}
return 0;
}

DWORD WINAPI Thread2(LPVOID pParam)
{
while(true)
{
if (g_nCount > 99)
break;

WaitForSingleObject(g_hMutex, INFINITE);
Sleep(
1);
cout
<<"Thread2:" << g_nCount++ << endl;
ReleaseMutex(g_hMutex);
}
return 0;
}


int main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1
= ::CreateThread(NULL, 0, Thread1, NULL, 0, NULL);
hThread2
= ::CreateThread(NULL, 0, Thread2, NULL, 0, NULL);

::CloseHandle(hThread1);
::CloseHandle(hThread2);

g_hMutex
= CreateMutex(NULL, FALSE, NULL);

while(true)
Sleep(
99 * 1000);
return 0;
}