1、MFC對多線程編程的支持編程
MFC中有兩類線程,分別稱之爲工做者線程和用戶界面線程。兩者的主要區別在於工做者線程沒有消息循環,而用戶界面線程有本身的消息隊列和消息循環。
工做者線程沒有消息機制,一般用來執行後臺計算和維護任務,如冗長的計算過程,打印機的後臺打印等。用戶界面線程通常用於處理獨立於其餘線程執行以外的用戶輸入,響應用戶及系統所產生的事件和消息等。但對於Win32的API編程而言,這兩種線程是沒有區別的,它們都只需線程的啓動地址便可啓動線程來執行任務。
在MFC中,通常用全局函數AfxBeginThread()來建立並初始化一個線程的運行,該函數有兩種重載形式,分別用於建立工做者線程和用戶界面線程。兩種重載函數原型和參數分別說明以下:數組
(1) CWinThread* AfxBeginThread( 安全
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);//用於建立工做者線程服務器
PfnThreadProc:指向工做者線程的執行函數的指針,線程函數原型必須聲明以下: 網絡
UINT ExecutingFunction(LPVOID pParam);多線程
請注意,ExecutingFunction()應返回一個UINT類型的值,用以指明該函數結束的緣由。通常狀況下,返回0代表執行成功。 函數
(2) CWinThread* AfxBeginThread( 性能
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
); spa
pThreadClass 是指向 CWinThread 的一個導出類的運行時類對象的指針,該導出類定義了被建立的用戶界面線程的啓動、退出等;其它參數的意義同形式1。使用函數的這個原型生成的線程也有消息機制,在之後的例子中咱們將發現同主線程的機制幾乎同樣。操作系統
下面咱們對CWinThread類的數據成員及經常使用函數進行簡要說明。
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,UINT nStackSize=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
該函數中的dwCreateFlags、nStackSize、lpSecurityAttrs參數和API函數CreateThread中的對應參數有相同含義,該函數執行成功,返回非0值,不然返回0。
通常狀況下,調用AfxBeginThread()來一次性地建立並啓動一個線程,可是也能夠經過兩步法來建立線程:首先建立CWinThread類的一個對象,而後調用該對象的成員函數CreateThread()來啓動該線程。
virtual BOOL CWinThread::InitInstance();
重載該函數以控制用戶界面線程實例的初始化。初始化成功則返回非0值,不然返回0。用戶界面線程常常重載該函數,工做者線程通常不使用InitInstance()。
virtual int CWinThread::ExitInstance();
在線程終結前重載該函數進行一些必要的清理工做。該函數返回線程的退出碼,0表示執行成功,非0值用來標識各類錯誤。同InitInstance()成員函數同樣,該函數也只適用於用戶界面線程。
2、MFC中線程同步
在程序中使用多線程時,通常不多有多個線程能在其生命期內進行徹底獨立的操做。更多的狀況是一些線程進行某些處理操做,而其餘的線程必須對其處理結果進行了解。正常狀況下對這種處理結果的瞭解應當在其處理任務完成後進行。
若是不採起適當的措施,其餘線程每每會在線程處理任務結束前就去訪問處理結果,這就頗有可能獲得有關處理結果的錯誤瞭解。例如,多個線程同時訪問同一個全局變量,若是都是讀取操做,則不會出現問題。若是一個線程負責改變此變量的值,而其餘線程負責同時讀取變量內容,則不能保證讀取到的數據是通過寫線程修改後的。
爲了確保讀線程讀取到的是通過修改的變量,就必須在向變量寫入數據時禁止其餘線程對其的任何訪問,直至賦值過程結束後再解除對其餘線程的訪問限制。象這種保證線程能瞭解其餘線程任務處理結束後的處理結果而採起的保護措施即爲線程同步。
線程的同步可分用戶模式的線程同步和內核對象的線程同步兩大類。用戶模式中線程的同步方法主要有原子訪問和臨界區等方法。其特色是同步速度特別快,適合於對線程運行速度有嚴格要求的場合。
內核對象的線程同步則主要由事件、等待定時器、信號量以及信號燈等內核對象構成。因爲這種同步機制使用了內核對象,使用時必須將線程從用戶模式切換到內核模式,而這種轉換通常要耗費近千個CPU週期,所以同步速度較慢,但在適用性上卻要遠優於用戶模式的線程同步方式。
1.臨界區
臨界區(Critical Section)是一段獨佔對某些共享資源訪問的代碼,在任意時刻只容許一個線程對共享資源進行訪問。若是有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後其餘全部試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其餘線程能夠繼續搶佔,並以此達到用原子方式操做共享資源的目的。
臨界區在使用時以CRITICAL_SECTION結構對象保護共享資源,並分別用EnterCriticalSection()和LeaveCriticalSection()函數去標識和釋放一個臨界區。所用到的CRITICAL_SECTION結構對象必須通過InitializeCriticalSection()的初始化後才能使用,並且必須確保全部線程中的任何試圖訪問此共享資源的代碼都處在此臨界區的保護之下。不然臨界區將不會起到應有的做用,共享資源依然有被破壞的可能。
CRITICAL_SECTION g_cs; // 臨界區結構對象
char g_cArray[10]; // 共享資源
UINT ThreadProc10(LPVOID pParam)
{
EnterCriticalSection(&g_cs); // 進入臨界區
for (int i = 0; i < 10; i++) // 對共享資源進行寫入操做
{
g_cArray[i] = 'a';
Sleep(1);
}
LeaveCriticalSection(&g_cs); // 離開臨界區
return 0;
}
UINT ThreadProc11(LPVOID pParam)
{
EnterCriticalSection(&g_cs);
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
LeaveCriticalSection(&g_cs);
return 0;
}
……
void CSample08View::OnCriticalSection()
{
InitializeCriticalSection(&g_cs); // 初始化臨界區
AfxBeginThread(ThreadProc10, NULL); // 啓動線程
AfxBeginThread(ThreadProc11, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
在使用臨界區時,通常不容許其運行時間過長,只要進入臨界區的線程尚未離開,其餘全部試圖進入此臨界區的線程都會被掛起而進入到等待狀態,並會在必定程度上影響。程序的運行性能。尤爲須要注意的是不要將等待用戶輸入或是其餘一些外界干預的操做包含到臨界區。若是進入了臨界區卻一直沒有釋放,一樣也會引發其餘線程的長時間等待。換句話說,在執行了EnterCriticalSection()語句進入臨界區後不管發生什麼,必須確保與之匹配的LeaveCriticalSection()都可以被執行到。能夠經過添加結構化異常處理代碼來確保LeaveCriticalSection()語句的執行。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。
MFC爲臨界區提供有一個CCriticalSection類,使用該類進行線程同步處理是很是簡單的,只需在線程函數中用CCriticalSection類成員函數Lock()和UnLock()標定出被保護代碼片斷便可。對於上述代碼,可經過CCriticalSection類將其改寫以下:
CCriticalSection g_clsCriticalSection; // MFC臨界區類對象
char g_cArray[10]; // 共享資源
UINT ThreadProc20(LPVOID pParam)
{
g_clsCriticalSection.Lock(); // 進入臨界區
for (int i = 0; i < 10; i++) // 對共享資源進行寫入操做
{
g_cArray[i] = 'a';
Sleep(1);
}
g_clsCriticalSection.Unlock(); // 離開臨界區
return 0;
}
UINT ThreadProc21(LPVOID pParam)
{
g_clsCriticalSection.Lock();
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
g_clsCriticalSection.Unlock();
return 0;
}
……
void CSample08View::OnCriticalSectionMfc()
{
AfxBeginThread(ThreadProc20, NULL);
AfxBeginThread(ThreadProc21, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
2.事件內核對象
在前面講述線程通訊時曾使用過事件內核對象來進行線程間的通訊,除此以外,事件內核對象也能夠經過通知操做的方式來保持線程的同步。對於前面那段使用臨界區保持線程同步的代碼可用事件對象的線程同步方法改寫以下:
HANDLE hEvent = NULL; // 事件句柄
char g_cArray[10]; // 共享資源
UINT ThreadProc12(LPVOID pParam)
{
WaitForSingleObject(hEvent, INFINITE); // 等待事件置位
for (int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
SetEvent(hEvent); // 處理完成後即將事件對象置位
return 0;
}
UINT ThreadProc13(LPVOID pParam)
{
WaitForSingleObject(hEvent, INFINITE);
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
SetEvent(hEvent);
return 0;
}
……
void CSample08View::OnEvent()
{
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // 建立事件
SetEvent(hEvent); // 事件置位
AfxBeginThread(ThreadProc12, NULL); // 啓動線程
AfxBeginThread(ThreadProc13, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
在建立線程前,首先建立一個能夠自動復位的事件內核對象hEvent,而線程函數則經過WaitForSingleObject()等待函數無限等待hEvent的置位,只有在事件置位時WaitForSingleObject()纔會返回,被保護的代碼將得以執行。對於以自動復位方式建立的事件對象,在其置位後一被WaitForSingleObject()等待到就會當即復位,也就是說在執行ThreadProc12()中的受保護代碼時,事件對象已是復位狀態的,這時即便有ThreadProc13()對CPU的搶佔,也會因爲WaitForSingleObject()沒有hEvent的置位而不能繼續執行,也就沒有可能破壞受保護的共享資源。在ThreadProc12()中的處理完成後能夠經過SetEvent()對hEvent的置位而容許ThreadProc13()對共享資源g_cArray的處理。這裏SetEvent()所起的做用能夠看做是對某項特定任務完成的通知。
使用臨界區只能同步同一進程中的線程,而使用事件內核對象則能夠對進程外的線程進行同步,其前提是獲得對此事件對象的訪問權。能夠經過OpenEvent()函數獲取獲得,其函數原型爲:
HANDLE OpenEvent(
DWORD dwDesiredAccess, // 訪問標誌
BOOL bInheritHandle, // 繼承標誌
LPCTSTR lpName // 指向事件對象名的指針
);
若是事件對象已建立(在建立事件時須要指定事件名),函數將返回指定事件的句柄。對於那些在建立事件時沒有指定事件名的事件內核對象,能夠經過使用內核對象的繼承性或是調用DuplicateHandle()函數來調用CreateEvent()以得到對指定事件對象的訪問權。在獲取到訪問權後所進行的同步操做與在同一個進程中所進行的線程同步操做是同樣的。
若是須要在一個線程中等待多個事件,則用WaitForMultipleObjects()來等待。WaitForMultipleObjects()與WaitForSingleObject()相似,同時監視位於句柄數組中的全部句柄。這些被監視對象的句柄享有平等的優先權,任何一個句柄都不可能比其餘句柄具備更高的優先權。WaitForMultipleObjects()的函數原型爲:
DWORD WaitForMultipleObjects(
DWORD nCount, // 等待句柄數
CONST HANDLE *lpHandles, // 句柄數組首地址
BOOL fWaitAll, // 等待標誌
DWORD dwMilliseconds // 等待時間間隔
);
參數nCount指定了要等待的內核對象的數目,存放這些內核對象的數組由lpHandles來指向。fWaitAll對指定的這nCount個內核對象的兩種等待方式進行了指定,爲TRUE時當全部對象都被通知時函數纔會返回,爲FALSE則只要其中任何一個獲得通知就能夠返回。dwMilliseconds在這裏的做用與在WaitForSingleObject()中的做用是徹底一致的。若是等待超時,函數將返回WAIT_TIMEOUT。若是返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某個值,則說明全部指定對象的狀態均爲已通知狀態(當fWaitAll爲TRUE時)或是用以減去WAIT_OBJECT_0而獲得發生通知的對象的索引(當fWaitAll爲FALSE時)。若是返回值在WAIT_ABANDONED_0與WAIT_ABANDONED_0+nCount-1之間,則表示全部指定對象的狀態均爲已通知,且其中至少有一個對象是被丟棄的互斥對象(當fWaitAll爲TRUE時),或是用以減去WAIT_OBJECT_0表示一個等待正常結束的互斥對象的索引(當fWaitAll爲FALSE時)。 下面給出的代碼主要展現了對WaitForMultipleObjects()函數的使用。經過對兩個事件內核對象的等待來控制線程任務的執行與中途退出:
HANDLE hEvents[2]; // 存放事件句柄的數組
UINT ThreadProc14(LPVOID pParam)
{
DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); // 等待開啓事件
if (dwRet1 == WAIT_OBJECT_0) // 若是開啓事件到達則線程開始執行任務
{
AfxMessageBox("線程開始工做!");
while (true)
{
for (int i = 0; i < 10000; i++);
DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0); // 在任務處理過程當中等待結束事件
if (dwRet2 == WAIT_OBJECT_0 + 1) // 若是結束事件置位則當即終止任務的執行
break;
}
}
AfxMessageBox("線程退出!");
return 0;
}
……
void CSample08View::OnStartEvent()
{
for (int i = 0; i < 2; i++) // 建立線程
hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
AfxBeginThread(ThreadProc14, NULL); // 開啓線程
SetEvent(hEvents[0]); // 設置事件0(開啓事件)
}
void CSample08View::OnEndevent()
{
SetEvent(hEvents[1]); // 設置事件1(結束事件)
}
MFC爲事件相關處理也提供了一個CEvent類,共包含有除構造函數外的4個成員函數PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分別至關與Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函數。而構造函數則履行了原CreateEvent()函數建立事件對象的職責,其函數原型爲:
CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
3.信號量內核對象
信號量(Semaphore)內核對象對線程的同步方式與前面幾種方法不一樣,它容許多個線程在同一時刻訪問同一資源,可是須要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()建立信號量時即要同時指出容許的最大資源計數和當前可用資源計數。通常是將當前可用資源計數設置爲最大資源計數,每增長一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就能夠發出信號量信號。可是當前可用計數減少到0時則說明當前佔用資源的線程數已經達到了所容許的最大數目,不能在容許其餘線程的進入,此時的信號量信號將沒法發出。線程在處理完共享資源後,應在離開的同時經過ReleaseSemaphore()函數將當前可用資源計數加1。在任什麼時候候當前可用資源計數決不可能大於最大資源計數。
使用信號量內核對象進行線程同步主要會用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數。其中,CreateSemaphore()用來建立一個信號量內核對象,其函數原型爲:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全屬性指針
LONG lInitialCount, // 初始計數
LONG lMaximumCount, // 最大計數
LPCTSTR lpName // 對象名指針
);
參數lMaximumCount是一個有符號32位值,定義了容許的最大資源計數,最大取值不能超過4294967295。lpName參數能夠爲建立的信號量定義一個名字,因爲其建立的是一個內核對象,所以在其餘進程中能夠經過該名字而獲得此信號量。OpenSemaphore()函數便可用來根據信號量名打開在其餘進程中建立的信號量,函數原型以下:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, // 訪問標誌
BOOL bInheritHandle, // 繼承標誌
LPCTSTR lpName // 信號量名
);
在線程離開對共享資源的處理時,必須經過ReleaseSemaphore()來增長當前可用資源計數。不然將會出現當前正在處理共享資源的實際線程數並無達到要限制的數值,而其餘線程卻由於當前可用資源計數爲0而仍沒法進入的狀況。ReleaseSemaphore()的函數原型爲:
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 信號量句柄
LONG lReleaseCount, // 計數遞增數量
LPLONG lpPreviousCount // 先前計數
);
該函數將lReleaseCount中的值添加給信號量的當前資源計數,通常將lReleaseCount設置爲1,若是須要也能夠設置其餘的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在試圖進入共享資源的線程函數入口處,主要用來判斷信號量的當前可用資源計數是否容許本線程的進入。只有在當前可用資源計數值大於0時,被監視的信號量內核對象纔會獲得通知。
信號量的使用特色使其更適用於對Socket(套接字)程序中線程的同步。例如,網絡上的HTTP服務器要對同一時間內訪問同一頁面的用戶數加以限制,這時能夠爲沒一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,經過使用信號量對線程的同步做用能夠確保在任一時刻不管有多少用戶對某一頁面進行訪問,只有不大於設定的最大用戶數目的線程可以進行訪問,而其餘的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問後纔有可能進入。下面給出的示例代碼即展現了相似的處理過程:
HANDLE hSemaphore; // 信號量對象句柄
UINT ThreadProc15(LPVOID pParam)
{
WaitForSingleObject(hSemaphore, INFINITE); // 試圖進入信號量關口
AfxMessageBox("線程一正在執行!"); // 線程任務處理
ReleaseSemaphore(hSemaphore, 1, NULL); // 釋放信號量計數
return 0;
}
UINT ThreadProc16(LPVOID pParam)
{
WaitForSingleObject(hSemaphore, INFINITE);
AfxMessageBox("線程二正在執行!");
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc17(LPVOID pParam)
{
WaitForSingleObject(hSemaphore, INFINITE);
AfxMessageBox("線程三正在執行!");
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
……
void CSample08View::OnSemaphore()
{
hSemaphore = CreateSemaphore(NULL, 2, 2, NULL); // 建立信號量對象
AfxBeginThread(ThreadProc15, NULL); // 開啓線程
AfxBeginThread(ThreadProc16, NULL);
AfxBeginThread(ThreadProc17, NULL);
}
在MFC中,經過CSemaphore類對信號量做了表述。該類只具備一個構造函數,能夠構造一個信號量對象,並對初始資源計數、最大資源計數、對象名和安全屬性等進行初始化,其原型以下:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
在構造了CSemaphore類對象後,任何一個訪問受保護共享資源的線程都必須經過CSemaphore從父類CSyncObject類繼承獲得的Lock()和UnLock()成員函數來訪問或釋放CSemaphore對象。與前面介紹的幾種經過MFC類保持線程同步的方法相似,經過CSemaphore類也能夠將前面的線程同步代碼進行改寫,這兩種使用信號量的線程同步方法不管是在實現原理上仍是從實現結果上都是徹底一致的。下面給出經MFC改寫後的信號量線程同步代碼:
// MFC信號量類對象
CSemaphore g_clsSemaphore(2, 2);
UINT ThreadProc24(LPVOID pParam)
{
// 試圖進入信號量關口
g_clsSemaphore.Lock();
// 線程任務處理
AfxMessageBox("線程一正在執行!");
// 釋放信號量計數
g_clsSemaphore.Unlock();
return 0;
}
UINT ThreadProc25(LPVOID pParam)
{
// 試圖進入信號量關口
g_clsSemaphore.Lock();
// 線程任務處理
AfxMessageBox("線程二正在執行!");
// 釋放信號量計數
g_clsSemaphore.Unlock();
return 0;
}
UINT ThreadProc26(LPVOID pParam)
{
// 試圖進入信號量關口
g_clsSemaphore.Lock();
// 線程任務處理
AfxMessageBox("線程三正在執行!");
// 釋放信號量計數
g_clsSemaphore.Unlock();
return 0;
}
……
void CSample08View::OnSemaphoreMfc()
{
// 開啓線程
AfxBeginThread(ThreadProc24, NULL);
AfxBeginThread(ThreadProc25, NULL);
AfxBeginThread(ThreadProc26, NULL);
}
4.互斥內核對象
互斥(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);
其惟一的參數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類改寫後的程序實現清單:
CMutex g_clsMutex(FALSE, NULL); // MFC互斥類對象
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); }