互斥对象(mutex)能够确保线程拥有对单个资源的互斥访问权。实际上互斥对象是因此而得名的。互斥对象包含一个使用数量,一个线程I D和一个递归计数器。互斥对象的行为特性与临界区相同,但是互斥对象属于内核对象,而临界区则属于用户方式对象。这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。
只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
1、SDK方式使用互斥
若要使用互斥对象,必须有一个进程首先调用CreateMutex,以便创建互斥对象:
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);
psa参数,关于安全性的,可参见《线程的同步(二)---事件》。
fInitialOwner参数用于控制互斥对象的初始状态。如果传递FALSE(这是通常情况下传递的值),那么互斥对象的ID和递归计数器均被设置为0,这意味着该互斥对象没有被任何线程所拥有,即初始化为有信息状态。
如果为fInitialOwner参数传递TRUE,那么该对象的线程ID被设置为调用线程的ID,递归计数器被设置为1。由于ID是个非0数字,因此该互斥对象初始为无信号状态,需要在调用线程中将其设置为有信号状态。
另外,通过调用OpenMutex,另一个进程可以获得它自己进程与现有互斥对象相关的句柄:
HANDLE OpenMutex(
DWORD fdwAccess,
BOOL bInheritHandle,
PCTSTR pszName);
通过调用一个等待函数(比如WaitForSingleObject函数),并传递负责保护资源的互斥对象的句柄,线程就能够获得对共享资源的访问权。在内部,等待函数要检查线程的ID,以了解它是否是0(互斥对象为有信号状态)。如果线程ID是0,那么该线程ID被设置为调用线程的ID,递归计数器被设置为1,同时,调用线程保持可调度状态。
如果等待函数发现ID不是0(互斥对象为无信号状态),那么调用线程便进入等待状态。系统将记住这个情况,并且在互斥对象的ID重新设置为0时,将线程ID设置为等待线程的ID,将递归计数器设置为1,并且允许等待线程再次成为可调度线程。与所有情况一样,对互斥内核对象进行的检查和修改都是以原子操作方式进行的。
对于互斥对象来说,正常的内核对象的已通知和未通知规则存在一个特殊的异常情况。比如说,一个线程试图等待一个未通知的互斥对象。在这种情况下,该线程通常被置于等待状态。然而,系统要查看试图获取互斥对象的线程的I D是否与互斥对象中记录的线程I D相同。如果两个线程ID相同,即使互斥对象处于未通知状态,系统也允许该线程保持可调度状态。我们不认为该“异常”行为特性适用于系统中的任何地方的其他内核对象。每当线程成功地等待互斥对象时,该对象的递归计数器就递增。若要使递归计数器的值大于1,唯一的方法是线程多次等待相同的互斥对象,以便利用这个异常规则。
一旦线程成功地等待到一个互斥对象,该线程就知道它已经拥有对受保护资源的独占访问权。试图访问该资源的任何其他线程(通过等待相同的互斥对象)均被置于等待状态中。当目前拥有对资源的访问权的线程不再需要它的访问权时,它必须调用ReleaseMutex函数来释放该互斥对象:
BOOL ReleaseMutex(HANDLE hMutex);
该函数将对象的递归计数器递减1。如果线程多次成功地等待一个互斥对象,在互斥对象的递归计数器变成0之前,该线程必须以同样的次数调用ReleaseMutex函数。当递归计数器到达0时,该线程ID也被置为0,同时该对象变为已通知状态。
当该对象变为已通知状态时,系统要查看是否有任何线程正在等待互斥对象。如果有,系统将“按公平原则”选定等待线程中的一个,为它赋予互斥对象的所有权。当然,这意味着线程I D被设置为选定的线程的ID,并且递归计数器被置为1。如果没有其他线程正在等待互斥对象,那么该互斥对象将保持已通知状态,这样,等待互斥对象的下一个线程就立即可以得到互斥对象。
2、使用MFC的CMutex类
CMutex的使用比较简单,同CCriticalSection一样。
用法一:
CMutex mutexObj;
mutexObj.Lock();
...
mutexObj.Unlock();
用法二:
CMutex mutexObj;
CSingleLock singleLock(&mutexObj);
singleLock.Lock();
...
singleLock.Unlock();