Windows提供了多組支持多線程的應(yīng)用程序接口(API)函數(shù)。許多讀者已經(jīng)對(duì)Windows提供的多線程函數(shù)有一定程度的了解,但是對(duì)于那些不熟悉這些的讀者,本章提供了這些函數(shù)的概述。記住,Windows提供了許多其他的基于多線程的函數(shù),這些函數(shù)需要您自己去探索。
為了使用Windows的多線程函數(shù),必須在程序中包含<Windows.h>。
3.4.1 線程的創(chuàng)建和終止
Windows API提供了CreateThread()函數(shù)來(lái)創(chuàng)建一個(gè)線程。其原型如下所示:
HANDLE CreateThread(LPSECURITY_ATTRIBUTES secAttr,
SIZE_T stackSize,
LPTHREAD_START_ROUTINE threadFunc,
LPVOID param,
DWORD flags,
LPDWORD threadID);
在此,secAttr是一個(gè)用來(lái)描述線程的安全屬性的指針。如果secAttr是NULL,就會(huì)使用默認(rèn)的安全描述符。
每個(gè)線程都具有自己的堆棧。可以使用stackSize參數(shù)來(lái)按字節(jié)指定新線程堆棧的大小。如果這個(gè)整數(shù)值為0,那么這個(gè)線程堆棧的大小與創(chuàng)建它的線程相同。如果需要的話,這個(gè)堆棧可以擴(kuò)展。(通常使用0來(lái)指定線程堆棧的大小)。
每個(gè)線程都在創(chuàng)建它的進(jìn)程中通過(guò)調(diào)用線程函數(shù)來(lái)開(kāi)始執(zhí)行。線程的執(zhí)行一直持續(xù)到線程函數(shù)返回。這個(gè)函數(shù)的地址(也就是線程的入口點(diǎn))在threadFunc中指定。每個(gè)線程函數(shù)都必須具有這樣的原型:
DWORD WINAPI threadfunc(LPVOID param);
需要傳遞給新線程的任何參數(shù)都在CreateThread()的param中指定。線程函數(shù)在它的參數(shù)中接收這個(gè)32位的值。這個(gè)參數(shù)可以用作任何目的。函數(shù)返回它的退出狀態(tài)。
參數(shù)flags確定了線程的執(zhí)行狀態(tài)。如果它是0,線程會(huì)立即執(zhí)行。如果是CREATE_SUSPEND,線程則以掛起狀態(tài)創(chuàng)建并等待執(zhí)行。(可以通過(guò)調(diào)用ResumeThread()來(lái)開(kāi)始執(zhí)行,稍后討論)。
與線程相關(guān)的標(biāo)識(shí)符以threadID所指向的長(zhǎng)整型返回。
如果成功,函數(shù)則向線程返回一個(gè)句柄。如果失敗,則返回NULL。可以通過(guò)調(diào)用CloseHandle()來(lái)顯式銷毀這個(gè)線程。否則,會(huì)在父進(jìn)程結(jié)束時(shí)自動(dòng)銷毀它。
如前所述,當(dāng)線程的入口函數(shù)返回時(shí)終止執(zhí)行線程。進(jìn)程也可以使用TerminateThread()或者ExitThread()來(lái)手動(dòng)終止線程,這兩個(gè)函數(shù)的原型如下:
BOOL TerminateThread(HANDLE thread, DWORD status);
VOID ExitThread(DWORD status);
對(duì)于TerminateThread(),thread是將要終止的線程的句柄。ExitThread()只能用來(lái)終止調(diào)用了ExitThread()的線程。對(duì)于兩個(gè)函數(shù)而言,status是終止?fàn)顟B(tài)。TerminateThread()如果成功,則會(huì)返回非0值,否則返回0。
調(diào)用ExitThread()在功能上等價(jià)于允許線程函數(shù)正常返回。這意味著堆棧會(huì)正確地重新設(shè)置。當(dāng)使用TerminateThread()結(jié)束線程時(shí),線程會(huì)立刻終止,而會(huì)執(zhí)行任何特定的清理任務(wù)。另外,TerminateThread()可能會(huì)停止正在執(zhí)行重要操作的線程。為此,當(dāng)入口函數(shù)返回時(shí),通常最好(也是最容易的)讓線程正常終止。
3.4.2 Visual C++對(duì)CreateThread()和ExitThread()的替換
盡管CreateThread()和ExitThread()是用來(lái)創(chuàng)建并終止線程的Windows API函數(shù),我們?cè)诒菊虏⒉粫?huì)使用它們。原因是在Visual C++中(其他的Windows兼容的編譯器也可能有這個(gè)問(wèn)題)使用這兩個(gè)函數(shù)時(shí),會(huì)導(dǎo)致內(nèi)存泄漏,丟失少量的內(nèi)存。對(duì)于Visual C++,如果多線程程序利用了C/C++標(biāo)準(zhǔn)庫(kù)函數(shù)并使用了CreateThread()和ExitThread(),就會(huì)丟失少量的內(nèi)存。(如果您的程序沒(méi)有使用C/C++的標(biāo)準(zhǔn)庫(kù),就不會(huì)發(fā)生這樣的內(nèi)存丟失)。為了避免這種情況,必須使用Visual C++運(yùn)行庫(kù)中定義的函數(shù)來(lái)開(kāi)始和終止線程,而不是使用由Win32 API指定的函數(shù)。這些函數(shù)類似于CreateThread()和ExitThread(),但是不會(huì)產(chǎn)生內(nèi)存泄漏。
提示:
如果使用非Visual C++的編譯器,如果需要的話,檢查它的文檔來(lái)確定是否需要忽略CreateThread()和ExitThread(),以及如何做到這一點(diǎn)。
Visual C++用_beginthreadex()和_endthreadex()來(lái)取代CreateThread()和ExitThread()。這兩個(gè)函數(shù)都需要頭文件<process.h>。下面是_beginthreadex()函數(shù)的原型:
uintptr_t _beginthreadex(void *secAttr, unsigned stackSize,
unsigned (__stdcall *threadFunc)(void *),
void *param, unsigned flags,
unsigned *threadID);
正如您看到的那樣,_beginthreadex()的參數(shù)類似于CreateThread()的參數(shù)。另外,這些參數(shù)與CreateThread()指定的參數(shù)有相同的含義。secAttr是一個(gè)用來(lái)描述線程安全性屬性的指針。然而,如果secAttr為NULL,則會(huì)使用默認(rèn)的安全描述符。新線程堆棧的大小由stackSize參數(shù)按字節(jié)數(shù)傳遞。如果這個(gè)值為0,那么這個(gè)線程堆棧的大小與進(jìn)程中創(chuàng)建它的主線程的大小相同。
線程函數(shù)的地址(也就是線程的入口點(diǎn))在threadFunc中指定。對(duì)于_beginthreadex(),線程函數(shù)的原型如下:
unsigned_stdcall threadfunc(void *param)
這個(gè)原型在功能上等價(jià)于CreateThread()的線程函數(shù)的原型,但是它使用了不同的類型名稱。想要傳遞給新線程的任何參數(shù)都在param參數(shù)中指定。
flags參數(shù)確定線程的執(zhí)行狀態(tài)。如果flags參數(shù)為0,線程會(huì)立即開(kāi)始執(zhí)行。如果flags參數(shù)為CREATE_SUSPEND,則以掛起狀態(tài)創(chuàng)建線程。(可以調(diào)用ResumeThread()來(lái)開(kāi)始它)。與線程相關(guān)的標(biāo)識(shí)符以threadID指向的double word返回。
如果成功,則這個(gè)函數(shù)返回一個(gè)線程的句柄;如果失敗,則返回0。類型uintptr_t指定了可以擁有指針或者句柄的Visual C++類型。
_endthreadex()的原型如下:
void _endthreadex(unsigned status);
它的功能就像ExitThread()那樣,停止線程并返回status中指定的退出代碼(exit code)。
由于Windows下使用最廣泛的編譯器是Visual C++,因此本章示例將使用_beginthreadex()和_endthreadex()而不是使用它們的等價(jià)的API函數(shù)。如果您使用了非Visual C++的編譯器,那么只需要用CreateThread()和EndThread()來(lái)替代這兩個(gè)函數(shù)。
當(dāng)使用_beginthreadex()和_endthreadex()時(shí),必須記住鏈接多線程庫(kù)。這隨編譯器的不同而不同。在此有一些示例。當(dāng)使用Visual C++的命令行編譯器時(shí),包括-MT選項(xiàng)。為了在Visual C++ 6 IDE中使用多線程庫(kù),首先要激活“Project | Settings”屬性頁(yè)。然后選擇“C/C++”選項(xiàng)卡。接著在“Category”下拉列表框中選擇“Code Generation”,然后在“Use Runtime Library ”下拉列表框中選擇“Multithreaded”。對(duì)于Visual C++ 7 .NET IDE,選擇“Project |Properties”。然后選擇“C/C++”條目,并加亮顯示“Code Generation”。最后,將“Multithreaded”選擇為運(yùn)行庫(kù)。
3.4.3 線程的掛起和恢復(fù)
線程的執(zhí)行可以通過(guò)調(diào)用SuspendThread()來(lái)掛起。可以通過(guò)調(diào)用ResumeThread()來(lái)恢復(fù)它。這兩個(gè)函數(shù)的原型如下:
DWORD SuspendThread(HANDLE hThread);
DWORD ResumeThread(HANDLE hThread);
兩個(gè)函數(shù)都通過(guò)hThread來(lái)傳遞線程的句柄。
每個(gè)執(zhí)行的線程都有與其相關(guān)的掛起計(jì)數(shù)。如果這個(gè)計(jì)數(shù)為0,那么不會(huì)掛起線程。如果為非0值,則線程就會(huì)處于掛起狀態(tài)。每次調(diào)用SuspendThread()都會(huì)增加這個(gè)計(jì)數(shù)。每次調(diào)用ResumeThread()都會(huì)減小這個(gè)掛起計(jì)數(shù)。掛起的線程只有在它的掛起計(jì)數(shù)達(dá)到0時(shí)才會(huì)恢復(fù)。因此,為了恢復(fù)一個(gè)掛起的線程,對(duì)ResumeThread()的調(diào)用次數(shù)必須與對(duì)SuspendThread()的調(diào)用次數(shù)相等。
這兩個(gè)函數(shù)都返回線程先前的掛起計(jì)數(shù),如果發(fā)生錯(cuò)誤,返回值為–1。
3.4.4 改變線程的優(yōu)先級(jí)
在Windows中,每個(gè)線程都與一個(gè)優(yōu)先級(jí)設(shè)置相關(guān)。線程的優(yōu)先級(jí)決定了線程接收的CPU時(shí)間的多少。低優(yōu)先級(jí)的線程接收比較少的時(shí)間,高優(yōu)先級(jí)的線程接收比較多的時(shí)間。當(dāng)然,線程接收的CPU時(shí)間的多少對(duì)于它的執(zhí)行性能以及它與系統(tǒng)中當(dāng)前執(zhí)行的其他線程之間的交互有著深遠(yuǎn)的影響。
在Windows中,線程優(yōu)先級(jí)的設(shè)置是兩個(gè)值的組合:進(jìn)程總體的優(yōu)先級(jí)類別以及相對(duì)于這個(gè)優(yōu)先級(jí)類別的各個(gè)線程的優(yōu)先級(jí)設(shè)置。也就是說(shuō),線程實(shí)際的優(yōu)先級(jí)由進(jìn)程的優(yōu)先級(jí)類別和各個(gè)線程的優(yōu)先級(jí)的組合來(lái)確定。后面會(huì)逐一講述。
1. 優(yōu)先級(jí)類別
在默認(rèn)情況下,進(jìn)程具有普通的優(yōu)先級(jí)類別,大多數(shù)程序在其執(zhí)行的聲明周期內(nèi)保持這個(gè)普通的優(yōu)先級(jí)類別。盡管在本章的示例中沒(méi)有改變優(yōu)先級(jí)類別,但是為了完整起見(jiàn),在此給出了線程優(yōu)先級(jí)類別的簡(jiǎn)單概況。
Windows定義了6個(gè)優(yōu)先級(jí)類別,相應(yīng)的值以從高到低的順序顯示如下:
REALTIME_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
ABOVE_NORMAL_PRIORITY_CLASS
NORMAL_PRIORITY_CLASS
BELOW_NORMAL_PRIORITY_CLASS
IDLE_PRIORITY_CLASS
在默認(rèn)情況下,程序的優(yōu)先級(jí)類別為NORMAL_PRIORITY_CLASS。通常,您不需要改變程序的優(yōu)先級(jí)類別。事實(shí)上,改變進(jìn)程的優(yōu)先級(jí)類別對(duì)于整個(gè)計(jì)算機(jī)系統(tǒng)的性能會(huì)有負(fù)面的影響。例如,如果您將一個(gè)程序的優(yōu)先級(jí)類別增加到REALTIME_PRIORITY_CLASS,它就會(huì)支配CPU。對(duì)于某些特殊的應(yīng)用程序,可能需要增加應(yīng)用程序的優(yōu)先級(jí)類別,但通常并不需要。如前所述,本章的應(yīng)用程序沒(méi)有改變優(yōu)先級(jí)類別。
當(dāng)確實(shí)需要改變程序的優(yōu)先級(jí)類別時(shí),可以調(diào)用SetPriorityClass()。可以調(diào)用GetPriorityClass()來(lái)獲取當(dāng)前的優(yōu)先級(jí)類別。這兩個(gè)函數(shù)的原型如下:
DWORD GetPriorityClass(HANDLE hApp);
BOOL SetPriorityClass(HANDLE hApp, DWORD priority);
在此,hApp是進(jìn)程的句柄。GetPriorityClass()返回應(yīng)用程序的優(yōu)先級(jí)類別,如果失敗的話,返回0。對(duì)于SetPriorityClass(),priority指定了進(jìn)程的新優(yōu)先級(jí)類別。
2. 線程優(yōu)先級(jí)
對(duì)于給定的優(yōu)先級(jí)類別,各個(gè)線程的優(yōu)先級(jí)確定了它在進(jìn)程內(nèi)接收的CPU時(shí)間的多少。當(dāng)線程第一次創(chuàng)建時(shí),它具有普通的優(yōu)先級(jí),但是您可以改變線程的優(yōu)先級(jí)—— 即使在它執(zhí) 行時(shí)。
可以通過(guò)調(diào)用GetThreadPriority()來(lái)獲取線程的優(yōu)先級(jí)設(shè)置。可以使用SetThreadPriority()來(lái)增加或者減小線程的優(yōu)先級(jí)。這兩個(gè)函數(shù)的原型如下:
BOOL SetThreadPriority(HANDLE hThread, int priority);
int GetThreadPriority(HANDLE hThread);
對(duì)于這兩個(gè)函數(shù)而言,hThread是線程的句柄。對(duì)于SetThreadPriority(),priority是新的優(yōu)先級(jí)設(shè)置。如果發(fā)生錯(cuò)誤,則返回值為0;否則,返回非0值。GetThreadPriority()會(huì)返回當(dāng)前的優(yōu)先級(jí)設(shè)置。優(yōu)先級(jí)設(shè)置按照從高到低的順序如表3-1所示。
表3-1 優(yōu)先級(jí)設(shè)置
線程優(yōu)先級(jí)
|
值
|
THREAD_PRIORITY_TIME_CRITICAL
|
15
|
THREAD_PRIORITY_HIGHEST
|
2
|
THREAD_PRIORITY_ABOVE_NORMAL
|
1
|
THREAD_PRIORITY_NORMAL
|
0
|
THREAD_PRIORITY_BELOW_NORMAL
|
-1
|
THREAD_PRIORITY_LOWEST
|
-2
|
THREAD_PRIORITY_IDLE
|
-15
|
這些值相對(duì)于進(jìn)程的優(yōu)先級(jí)類別或增或減。通過(guò)組合進(jìn)程的優(yōu)先級(jí)類別和線程的優(yōu)先級(jí),Windows向應(yīng)用程序提供了31個(gè)不同的優(yōu)先級(jí)設(shè)置的支持。
如果有錯(cuò)誤發(fā)生,則GetThreadPriority()返回THREAD_PRIORITY_ERROR_RETURN。
在大多數(shù)情況下,如果線程具有普通的優(yōu)先級(jí)類別,那么可以隨意地改變它的優(yōu)先級(jí)設(shè)置,而不必?fù)?dān)心會(huì)給整個(gè)系統(tǒng)的性能帶來(lái)災(zāi)難性的影響。您將會(huì)看到,在下面部分開(kāi)發(fā)的線程控制面板中,可以改變進(jìn)程內(nèi)線程的優(yōu)先級(jí)設(shè)置(但是不能改變優(yōu)先級(jí)類別)。
3.4.5 獲取主線程的句柄
主線程的執(zhí)行是可以控制的。為此,需要獲取它的句柄。做到這一點(diǎn)最簡(jiǎn)單的方法是調(diào)用GetCurrentThread(),其原型如下:
HANDLE GetCurrentThread(void);
這個(gè)函數(shù)返回當(dāng)前線程的偽句柄(pseudohandle)。之所以稱之為偽句柄,是因?yàn)樗且粋€(gè)預(yù)定義的值,總是引用當(dāng)前的線程,而不是引用指定的調(diào)用線程。然而,它能夠用在任何可以使用普通線程處理的地方。
3.4.6 同步
在使用多線程或多進(jìn)程時(shí),有時(shí)需要調(diào)整兩個(gè)或者多個(gè)線程(或者進(jìn)程)之間的活動(dòng)。這個(gè)過(guò)程稱為同步。當(dāng)兩個(gè)或者多個(gè)線程需要訪問(wèn)共享資源,而這個(gè)共享資源在同一時(shí)刻只能由一個(gè)線程使用時(shí),就需要使用同步。例如,當(dāng)一個(gè)線程在寫(xiě)文件時(shí),在此時(shí)必須阻止另一個(gè)線程也這么做。同步的另一個(gè)原因是有時(shí)線程需要等待由另一個(gè)線程引發(fā)的事件。在此情況下,必須采取某種措施將第一個(gè)線程保持掛起狀態(tài),直到這個(gè)事件發(fā)生。隨后等待的線程必須恢復(fù) 執(zhí)行。
通常某個(gè)任務(wù)會(huì)處于兩種狀態(tài)。首先,它可能正在執(zhí)行(或者在獲得它的時(shí)間段時(shí)就開(kāi)始執(zhí)行)。另外,任務(wù)可能被阻塞,等待某個(gè)資源或者事件。在此情況下其執(zhí)行被掛起,直到所需的資源可以使用或者所等待的事件發(fā)生。
如果您對(duì)于同步問(wèn)題或者它的常用解決方案(信號(hào)量)不熟悉,下面的部分將對(duì)此進(jìn)行討論。
1. 理解同步問(wèn)題
Windows必須提供某種特殊的服務(wù)來(lái)允許對(duì)共享資源的訪問(wèn)同步,因?yàn)槿绻麤](méi)有操作系統(tǒng)的協(xié)助,進(jìn)程或者線程就沒(méi)有辦法得知它是否在單獨(dú)訪問(wèn)某個(gè)資源。為了理解這個(gè)問(wèn)題,假定您在為一個(gè)沒(méi)有提供任何同步支持的多任務(wù)操作系統(tǒng)編寫(xiě)程序,并且假定您具有兩個(gè)并發(fā)執(zhí)行的線程A和B,它們都不時(shí)地訪問(wèn)某個(gè)資源R(如磁盤(pán)文件),這個(gè)資源在某個(gè)時(shí)刻只能被一個(gè)線程訪問(wèn)。為了在一個(gè)線程使用這個(gè)資源時(shí)阻止另一個(gè)線程訪問(wèn)它,您嘗試了下面的解決方案。首先,創(chuàng)建了一個(gè)初始化值為0并且兩個(gè)線程都可以訪問(wèn)的變量,名為flag。然后,在使用訪問(wèn)R的每段代碼之前,等待flag被清0,然后設(shè)置flag,訪問(wèn)R,最后將flag清0。也就是說(shuō),在每個(gè)線程訪問(wèn)R之前,執(zhí)行如下的代碼:
while(flag) ; // wait for flag to be cleared
flag = 1; // set flag
// ... access resource R ...
flag = 0; // clear the flag
這段代碼隱含的概念是,如果設(shè)置了flag,則兩個(gè)線程都不能夠訪問(wèn)R。從概念上講,這種方法是正確的解決方案。然而,實(shí)際上它遠(yuǎn)遠(yuǎn)沒(méi)有達(dá)到要求,原因很簡(jiǎn)單:它并非總是有效!讓我們看一下原因。
使用剛才給定的代碼,有可能兩個(gè)進(jìn)程同時(shí)訪問(wèn)R。while循環(huán)在本質(zhì)上執(zhí)行重復(fù)的加載和比較flag上的指令。換句話說(shuō),它一直在測(cè)試flag的值。當(dāng)flag被清0的時(shí)候,代碼的下一行將設(shè)置flag的值。問(wèn)題在于,這兩個(gè)操作有可能在兩個(gè)不同的時(shí)間段執(zhí)行。在兩個(gè)時(shí)間段之間,flag的值有可能被另一個(gè)線程訪問(wèn),從而R被兩個(gè)線程同時(shí)訪問(wèn)。為了理解這一點(diǎn),假定線程A進(jìn)入while循環(huán),發(fā)現(xiàn)flag為0,這是訪問(wèn)R的綠燈。然而,在將flag設(shè)置為1之前,其時(shí)間段用盡,線程B恢復(fù)執(zhí)行。如果B執(zhí)行了它的while,它也發(fā)現(xiàn)flag沒(méi)有被設(shè)置,并且認(rèn)為它可以安全地訪問(wèn)R。然而,當(dāng)A重新開(kāi)始時(shí),它也會(huì)訪問(wèn)R。問(wèn)題的關(guān)鍵在于對(duì)flag的測(cè)試和設(shè)置沒(méi)有包含在一個(gè)連續(xù)的操作中,而是可以被分為兩個(gè)部分,正如剛才說(shuō)明的那樣。無(wú)論您如何努力,都沒(méi)有辦法只使用應(yīng)用層的代碼以絕對(duì)保證在同一時(shí)刻只有一個(gè)線程訪問(wèn)R。
對(duì)同步問(wèn)題的解決方案簡(jiǎn)單而優(yōu)雅。操作系統(tǒng)(在Windows中)提供了一個(gè)例程,在一個(gè)連續(xù)的操作中完成對(duì)flag的測(cè)試和設(shè)置(如果可能的話)。用操作系統(tǒng)工程師的話來(lái)說(shuō),這就是所謂的測(cè)試和置位(test and set)操作。由于歷史的原因,用來(lái)控制對(duì)共享資源的訪問(wèn)并提供線程(以及進(jìn)程)間同步的標(biāo)記被稱為信號(hào)量。信號(hào)量是Windows同步系統(tǒng)的核心。
2. Windows的同步對(duì)象
Windows支持幾種類型的同步對(duì)象。第一種類型是經(jīng)典的信號(hào)量。當(dāng)使用信號(hào)量時(shí),可以完全同步資源,在此情況下只有一個(gè)進(jìn)程或者線程在同一時(shí)刻可以訪問(wèn)這個(gè)資源,或者信號(hào)量允許不超過(guò)一定數(shù)量的進(jìn)程或者線程在同一時(shí)刻訪問(wèn)資源。信號(hào)量使用計(jì)數(shù)器來(lái)實(shí)現(xiàn),當(dāng)某個(gè)任務(wù)使用信號(hào)量時(shí),計(jì)數(shù)器減小;當(dāng)這個(gè)任務(wù)釋放信號(hào)量時(shí),計(jì)數(shù)器增加。
第二個(gè)同步對(duì)象是互斥體信號(hào)量,或者簡(jiǎn)稱為互斥體。互斥體將一個(gè)資源同步,保證在任何時(shí)候都只有一個(gè)線程或者進(jìn)程來(lái)訪問(wèn)它。在本質(zhì)上,互斥體是標(biāo)準(zhǔn)信號(hào)量的一個(gè)特殊版本。
第三個(gè)同步對(duì)象是事件對(duì)象。它可以用來(lái)阻塞對(duì)某個(gè)資源的訪問(wèn),直到某個(gè)其他的進(jìn)程或者線程發(fā)送信號(hào),通知可以使用資源(也就是一個(gè)事件對(duì)象發(fā)送某個(gè)指定的事件發(fā)生的信號(hào))。
第四個(gè)同步對(duì)象是可等待計(jì)時(shí)器。可等待計(jì)時(shí)器阻塞線程的執(zhí)行,直到指定的時(shí)間。也可以創(chuàng)建計(jì)時(shí)器序列,這是一個(gè)計(jì)時(shí)器的列表。
可以使用臨界區(qū)對(duì)象將一個(gè)代碼段放入臨界區(qū),從而阻止在同一時(shí)刻一個(gè)以上的線程使用這段代碼。當(dāng)一個(gè)線程進(jìn)入臨界區(qū)時(shí),其他線程只有在第一個(gè)線程離開(kāi)整個(gè)臨界區(qū)時(shí)才可以使用它。
本章使用的惟一的同步對(duì)象是互斥體,下面的部分將對(duì)其進(jìn)行描述。然而,C++程序員可以使用所有的Windows定義的同步對(duì)象。如前所述,這是使得C++依賴于操作系統(tǒng)處理多線程的主要優(yōu)點(diǎn)之一:所有的多線程特性都在您的控制之中。
3. 使用互斥體同步線程
如前所述,互斥體是一種特殊的信號(hào)量,在給定的時(shí)間內(nèi),只允許一個(gè)線程訪問(wèn)某個(gè)資源。在使用互斥體之前,必須使用CreatMutex()創(chuàng)建一個(gè)互斥體,函數(shù)原型如下:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES secAttr,
BOOL acquire,
LPCSTR name);
在此,secAttr是用來(lái)描述安全屬性的指針。如果secAttr為NULL,則使用默認(rèn)的安全描 述符。
如果創(chuàng)建的線程需要互斥體的控制,則acquire為true,否則為false。
name參數(shù)指向一個(gè)字符串,這個(gè)字符串是互斥體對(duì)象的名稱。互斥體是一個(gè)全局對(duì)象,它可能被其他進(jìn)程使用。為此,當(dāng)兩個(gè)進(jìn)程都打開(kāi)了使用相同名稱的互斥體時(shí),二者引用了相同的互斥體。使用這種方法可以將兩個(gè)進(jìn)程同步。這個(gè)名稱也可以為NULL,在此情況下這個(gè)信號(hào)量被限制在一個(gè)進(jìn)程之內(nèi)。
如果成功,則CreatMutex()函數(shù)返回信號(hào)量的句柄,否則,返回NULL。當(dāng)主進(jìn)程結(jié)束時(shí),互斥體的句柄則自動(dòng)關(guān)閉。當(dāng)不再需要時(shí),可以調(diào)用CloseHandle()來(lái)顯式地關(guān)閉互斥體的句柄。
當(dāng)創(chuàng)建信號(hào)量時(shí),可以調(diào)用兩個(gè)相關(guān)的函數(shù)來(lái)使用它:WaitForSingleObject()和ReleaseMutex()。這兩個(gè)函數(shù)的原型如下:
DWORD WaitForSingleObject(HANDLE hObject, DWORD howLong);
BOOL ReleaseMutex(HANDLE hMutex);
WaitForSingleObject()等待一個(gè)同步對(duì)象,直到這個(gè)對(duì)象可以使用或者超時(shí)之后才會(huì)返回。在使用互斥體時(shí),hObject是互斥體的句柄。howLong參數(shù)以毫秒為單位指定調(diào)用例程的等待時(shí)間。當(dāng)這個(gè)時(shí)間用盡時(shí),會(huì)返回超時(shí)錯(cuò)誤。為了無(wú)限期地等待,可以使用值INFINITE。當(dāng)成功時(shí)(也就是訪問(wèn)被準(zhǔn)許),這個(gè)函數(shù)返回WAIT_OBJECT_0。當(dāng)發(fā)生超時(shí)時(shí),返回WAIT_TIMEOUT。
ReleaseMutex()釋放互斥體,并允許其他線程獲取它。在此,hMutex是互斥體的句柄。如果成功,則函數(shù)返回非0值;如果失敗,則返回0。
為了使用互斥體控制對(duì)共享資源的訪問(wèn),封裝了訪問(wèn)在調(diào)用WaitForSingleObject()和ReleaseMutex()之間的資源的代碼,如下面的代碼所示(當(dāng)然,超時(shí)期限隨應(yīng)用程序的不同而 不同)。
if(WaitForSingleObject(hMutex, 10000)==WAIT_TIMEOUT) {
// handle time-out error
}
// access the resource
ReleaseMutex(hMutex);
通常,您會(huì)選擇足夠長(zhǎng)的超時(shí)期限來(lái)適應(yīng)程序的操作。如果在開(kāi)發(fā)多線程應(yīng)用程序時(shí)重復(fù)出現(xiàn)超時(shí)錯(cuò)誤,那么通常意味著您創(chuàng)建了死鎖條件。當(dāng)一個(gè)線程等待另一個(gè)線程永遠(yuǎn)都不會(huì)釋放的互斥體時(shí),就會(huì)發(fā)生死鎖。