互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。
只有拥有互斥对象并得以进入到共享资源,而其他线程则会被排斥在外。当此线程处理完共享资源并准备离开此区域时将把其所拥有的互斥对象交出,其他任何一个试图访问此资源的线程都有机会得到此互斥对象。
以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先要通过CreateMutex()或OpenMutex()创建或打开一个互斥对象。CreateMutex()函数原型为:
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针 BOOL bInitialOwner, // 初始拥有者 LPCTSTR lpName // 互斥对象名 );
参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex()函数得到此互斥对象的句柄。OpenMutex()函数原型为:
HANDLE OpenMutex( DWORD dwDesiredAccess, // 访问标志 BOOL bInheritHandle, // 继承标志 LPCTSTR lpName // 互斥对象名 );
当目前对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex()函数来释放其拥有的互斥对象,其函数原型为:
BOOL ReleaseMutex(HANDLE hMutex); //待释放的互斥对象句柄
至于WaitForSingleObject() 和WaitForMultipleObjects() 等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。
在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单:
// 互斥对象 HANDLE hMutex = NULL; char g_cArray[10]; UINT ThreadProc18(LPVOID pParam) { // 等待互斥对象通知 WaitForSingleObject(hMutex, INFINITE); // 对共享资源进行写入操作 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } // 释放互斥对象 ReleaseMutex(hMutex); return 0; } UINT ThreadProc19(LPVOID pParam) { // 等待互斥对象通知 WaitForSingleObject(hMutex, INFINITE); // 对共享资源进行写入操作 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } // 释放互斥对象 ReleaseMutex(hMutex); return 0; } …… void CSample08View::OnMutex() { // 创建互斥对象 hMutex = CreateMutex(NULL, FALSE, NULL); // 启动线程 AfxBeginThread(ThreadProc18, NULL); AfxBeginThread(ThreadProc19, NULL); // 等待计算完毕 Sleep(300); // 报告计算结果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); }
互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数,当完成对互斥对象保护资源的访问后,可通过调用从父类CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL,LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
该类的适用范围和实现原理与API方式创建的互斥内核对象是完全类似的,但要简洁的多,下面给出就是对前面的示例代码经CMutex类改写后的程序实现清单:
// MFC互斥类对象 CMutex g_clsMutex(FALSE, NULL); UINT ThreadProc27(LPVOID pParam) { // 等待互斥对象通知 g_clsMutex.Lock(); // 对共享资源进行写入操作 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } // 释放互斥对象 g_clsMutex.Unlock(); return 0; } UINT ThreadProc28(LPVOID pParam) { // 等待互斥对象通知 g_clsMutex.Lock(); // 对共享资源进行写入操作 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } // 释放互斥对象 g_clsMutex.Unlock(); return 0; } …… void CSample08View::OnMutexMfc() { // 启动线程 AfxBeginThread(ThreadProc27, NULL); AfxBeginThread(ThreadProc28, NULL); // 等待计算完毕 Sleep(300); // 报告计算结果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); }