互斥对象 ( mutex )内核对象能够确保线程拥有对单个资源的互斥访问权 。
互斥对象的组成
一个 使用数量
一个 线程 ID ( 用于标识系统中的哪个线程当前拥有互斥对象)
一个 递归计数器( 用于指明该线程拥有互斥对象的次数)
互斥对象的使用规则如下
• 如果线程 ID 是 0 (这是个无效 ID ),互斥对象不被任何线程所拥有,并且发出该互斥对象的通知信号。
• 如果 ID 是个非 0 数字,那么一个线程就拥有互斥对象,并且不发出该互斥对象的通知信号。
• 与所有其他内核对象不同, 互斥对象在操作系统中拥有特殊的代码,允许它们违反正常的规则(后面将要介绍这个异常情况)。
互斥对象作用
通常来说,互斥对象用于保护由多个线程访问的内存块 。如果多个线程要同时访问内存块,内存块中的数据就可能遭到破坏。互斥对象能够保证访问内存块的任何线程拥有对该内存块的独占访问权,这样就能够保证数据的完整性。
创建互斥对象
HANDLE CreateMutex (
PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);
psa 和 pszName 这两个参数(见 内核对象的安全性 和 跨越进程边界共享内核对象【命名对象】 )
fInitialOwner 参数 用于控制互斥对象的初始状态。如果传递 FALSE (这是通常情况下传递的值),那么互斥对象的 I D 和递归计数器均被设置为 0 。这意味着该互斥对象没有被任何线程所拥有,因此要发出它的通知信号。
如果为 fInitialOwner 参数 传递 TRUE ,那么该对象的线程 ID 被设置为调用线程的 ID ,递归计数器被设置为 1 。由于 ID 是个非 0 数字,因此该互斥对象开始时不发出通知信号。
打开互斥对象
通过调用 OpenMutex ,另一个进程可以获得它自己进程与现有互斥对象相关的句柄:
HANDLE OpenMutex (
DWORD fdwAccess,
BOOL bInheritHandle,
PCTSTR pszName);
通过调用一个等待函数 ,并传递负责保护资源的互斥对象的句柄,线程就能够获得对共享资源的访问权。在内部,等待函数要检查线程的 ID ,以了解它是否是 0 (互斥对象发出通知信号)。如果线程 ID 是 0 ,那么该线程 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 (试图释放不是调用者拥有的互斥对象)。
因此,如果在释放互斥对象之前,拥有互斥对象的线程终止运行(使 ExitThread 、 TerminateThread 、 ExitProcess 或 TerminateProcess 函数),那么互斥对象和正在等待互斥对象的其他线程将会发生什么情况呢?答案是,系统将把该互斥对象视为已经被放弃 —— 拥有互斥对象的线程决不会释放它,因为该线程已经终止运行。(上面介绍的情况提供了另一个例子,说明为什么决不应该调用 TerminateThread 函数 )。
由于系统保持对所有互斥对象和线程内核对象的跟踪,因此它能准确的知道互斥对象何时被放弃。 当一个互斥对象被放弃时,系统将自动把互斥对象的 ID 复置为 0 ,并将它的递归计数器复置为 0 。然后,系统要查看目前是否有任何线程正在等待该互斥对象。如果有,系统将 “ 公平地 ” 选定一个等待线程,将 ID 设置为选定的线程的 ID ,并将递归计数器设置为 1 ,同时,选定的线程变为可调度线程。
互斥对象和关键代码段的异同
互斥对象的行为特性与关键代码段相同 ,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。这意味着互斥对象的运行速度比关键代码段要慢 。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。
互斥对象与关键代码段的比较
特性 | 互斥对象 | 关键代码段 |
运行速度 | 慢 | 快 |
是否能够跨进程边界来使用 | 是 | 否 |
声明 | HANDLE hmtx ; | CRITICAL_SECTION cs ; |
初始化 | hmtx = CreateMutex ( NULL , FALSE , NULL ); | InitializeCriticalSe ction(&es) ; |
清除 | CloseHandle ( hmtx ); | DeleteCriticalSectio n ( &cs ); |
无限等待 | WaitForSingleObject ( hmtx ,IN FINITE ); | EnterCriticalSection ( &cs ); |
0 等待 | WaitForSingleObjectTry ( hmtx , 0 ); | EnterCriticalSection ( & c s ); |
任意等待 | WaitForSingleObject ( hmtx,dwMilliseconds ); | 不能 |
释放 | ReleaseMutex ( hmtx ); | LeaveCriticalSectio n ( &cs ); |
是否能够等待其他内核对象 | 是(使用 WaitForMultipleObjects 或类似的函数) | 否 |
例子
// -------------------------------------------------------------------------
// 文件名 : 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 ;
}
本文地址:http://www.cnblogs.com/fangyukuan/archive/2010/09/05/1818454.html