來(lái)自:夢(mèng)在天涯C++博客(http://m.shnenglu.com/mzty/)
一 線程
1)如果你正在編寫C/C++代碼,決不應(yīng)該調(diào)用CreateThread。相反,應(yīng)該使用VisualC++運(yùn)行期庫(kù)函數(shù)_beginthreadex,退出也應(yīng)該使用_endthreadex。如果不使用Microsoft的VisualC++編譯器,你的編譯器供應(yīng)商有它自己的CreateThred替代函數(shù)。不管這個(gè)替代函數(shù)是什么,你都必須使用。
2)因?yàn)開beginthreadex和_endthreadex是CRT線程函數(shù),所以必須注意編譯選項(xiàng)runtimelibaray的選擇,使用MT或MTD。
3) _beginthreadex函數(shù)的參數(shù)列表與CreateThread函數(shù)的參數(shù)列表是相同的,但是參數(shù)名和類型并不完全相同。這是因?yàn)镸icrosoft的C/C++運(yùn)行期庫(kù)的開發(fā)小組認(rèn)為,C/C++運(yùn)行期函數(shù)不應(yīng)該對(duì)Windows數(shù)據(jù)類型有任何依賴。_beginthreadex函數(shù)也像CreateThread那樣,返回新創(chuàng)建的線程的句柄。
4)下面是關(guān)于_beginthreadex的一些要點(diǎn):
•每個(gè)線程均獲得由C/C++運(yùn)行期庫(kù)的堆棧分配的自己的tiddata內(nèi)存結(jié)構(gòu)。(tiddata結(jié)構(gòu)位于Mtdll.h文件中的VisualC++源代碼中)。
•傳遞給_beginthreadex的線程函數(shù)的地址保存在tiddata內(nèi)存塊中。傳遞給該函數(shù)的參數(shù)也保存在該數(shù)據(jù)塊中。
•_beginthreadex確實(shí)從內(nèi)部調(diào)用CreateThread,因?yàn)檫@是操作系統(tǒng)了解如何創(chuàng)建新線程的唯一方法。
•當(dāng)調(diào)用CreatetThread時(shí),它被告知通過(guò)調(diào)用_threadstartex而不是pfnStartAddr來(lái)啟動(dòng)執(zhí)行新線程。還有,傳遞給線程函數(shù)的參數(shù)是tiddata結(jié)構(gòu)而不是pvParam的地址。
•如果一切順利,就會(huì)像CreateThread那樣返回線程句柄。如果任何操作失敗了,便返回NULL。
5) _endthreadex的一些要點(diǎn):
•C運(yùn)行期庫(kù)的_getptd函數(shù)內(nèi)部調(diào)用操作系統(tǒng)的TlsGetValue函數(shù),該函數(shù)負(fù)責(zé)檢索調(diào)用線程的tiddata內(nèi)存塊的地址。
•然后該數(shù)據(jù)塊被釋放,而操作系統(tǒng)的ExitThread函數(shù)被調(diào)用,以便真正撤消該線程。當(dāng)然,退出代碼要正確地設(shè)置和傳遞。
6) 雖然也提供了簡(jiǎn)化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。
6)線程handle因?yàn)槭莾?nèi)核對(duì)象,所以需要在最后close handle。
7)C++主線程的終止,同時(shí)也會(huì)終止所有主線程創(chuàng)建的子線程,不管子線程有沒(méi)有執(zhí)行完畢。
8)如果某線程掛起,然后有調(diào)用WaitForSingleObject等待該線程,就會(huì)導(dǎo)致死鎖。
二 線程同步之Critical Sections
1) 因?yàn)镃ritical Sections不是內(nèi)核對(duì)象,所以只能用來(lái)同一進(jìn)程內(nèi)線程間的同步,不能用來(lái)多個(gè)不同進(jìn)程間的線程的同步。
2) 如果在Critical Sections中間突然程序crash或是exit而沒(méi)有調(diào)用LeaveCriticalSection,則結(jié)果是該線程所對(duì)應(yīng)的內(nèi)核不能被釋放,該線程成為死線程。
3) 要比其他的內(nèi)核對(duì)象的速度要快。
4)很好的封裝:
class CritSect
{
public:
friend class Lock;
CritSect() { InitializeCriticalSection(&_critSection); }
~CritSect() { DeleteCriticalSection(&_critSection); }
private:
void Acquire(){EnterCriticalSection(&_critSection);}
void Release(){LeaveCriticalSection(&_critSection);}
CRITICAL_SECTION _critSection;
};
class Lock
{
public:
Lock(CritSect& critSect):_critSect(critSect) { _critSect.Acquire(); }
~Lock(){_critSect.Release();}
private:
CritSect& _critSect;
};
調(diào)用:CritSect sect;Lock lock(sect);
三 線程同步之Mutex
1)互斥對(duì)象(mutex)內(nèi)核對(duì)象能夠確保線程擁有對(duì)單個(gè)資源的互斥訪問(wèn)權(quán)。實(shí)際上互斥對(duì)象是因此而得名的?;コ鈱?duì)象包含一個(gè)使用數(shù)量,一個(gè)線程ID和一個(gè)遞歸計(jì)數(shù)器。
2) 互斥對(duì)象的行為特性與關(guān)鍵代碼段相同,但是互斥對(duì)象屬于內(nèi)核對(duì)象,而關(guān)鍵代碼段則屬于用戶方式對(duì)象。這意味著互斥對(duì)象的運(yùn)行速度比關(guān)鍵代碼段要慢。但是這也意味著不同進(jìn)程中的多個(gè)線程能夠訪問(wèn)單個(gè)互斥對(duì)象,并且這意味著線程在等待訪問(wèn)資源時(shí)可以設(shè)定一個(gè)超時(shí)值。
3) ID用于標(biāo)識(shí)系統(tǒng)中的哪個(gè)線程當(dāng)前擁有互斥對(duì)象,遞歸計(jì)數(shù)器用于指明該線程擁有互斥對(duì)象的次數(shù)。
4) 互斥對(duì)象有許多用途,屬于最常用的內(nèi)核對(duì)象之一。通常來(lái)說(shuō),它們用于保護(hù)由多個(gè)線程訪問(wèn)的內(nèi)存塊。如果多個(gè)線程要同時(shí)訪問(wèn)內(nèi)存塊,內(nèi)存塊中的數(shù)據(jù)就可能遭到破壞?;コ鈱?duì)象能夠保證訪問(wèn)內(nèi)存塊的任何線程擁有對(duì)該內(nèi)存塊的獨(dú)占訪問(wèn)權(quán),這樣就能夠保證數(shù)據(jù)的完整性。
5)互斥對(duì)象的使用規(guī)則如下:
• 如果線程ID是0(這是個(gè)無(wú)效ID),互斥對(duì)象不被任何線程所擁有,并且發(fā)出該互斥對(duì)象的通知信號(hào)。
• 如果ID是個(gè)非0數(shù)字,那么一個(gè)線程就擁有互斥對(duì)象,并且不發(fā)出該互斥對(duì)象的通知信號(hào)。
• 與所有其他內(nèi)核對(duì)象不同, 互斥對(duì)象在操作系統(tǒng)中擁有特殊的代碼,允許它們違反正常的規(guī)則。
四 線程同步之Event
1)在所有的內(nèi)核對(duì)象中,事件內(nèi)核對(duì)象是個(gè)最基本的對(duì)象。它們包含一個(gè)使用計(jì)數(shù)(與所有內(nèi)核對(duì)象一樣),一個(gè)用于指明該事件是個(gè)自動(dòng)重置的事件還是一個(gè)人工重置的事件的布爾值,另一個(gè)用于指明該事件處于已通知狀態(tài)還是未通知狀態(tài)的布爾值。
2)事件能夠通知一個(gè)操作已經(jīng)完成。有兩種不同類型的事件對(duì)象。一種是人工重置的事件,另一種是自動(dòng)重置的事件。當(dāng)人工重置的事件得到通知時(shí),等待該事件的所有線程均變?yōu)榭烧{(diào)度線程。當(dāng)一個(gè)自動(dòng)重置的事件得到通知時(shí),等待該事件的線程中只有一個(gè)線程變?yōu)榭烧{(diào)度線程。
3)當(dāng)一個(gè)線程執(zhí)行初始化操作,然后通知另一個(gè)線程執(zhí)行剩余的操作時(shí),事件使用得最多。事件初始化為未通知狀態(tài),然后,當(dāng)該線程完成它的初始化操作后,它就將事件設(shè)置為已通知狀態(tài)。這時(shí),一直在等待該事件的另一個(gè)線程發(fā)現(xiàn)該事件已經(jīng)得到通知,因此它就變成可調(diào)度線程。
4)Microsoft為自動(dòng)重置的事件定義了應(yīng)該成功等待的副作用規(guī)則,即當(dāng)線程成功地等待到該對(duì)象時(shí),自動(dòng)重置的事件就會(huì)自動(dòng)重置到未通知狀態(tài)。這就是自動(dòng)重置的事件如何獲得它們的名字的方法。通常沒(méi)有必要為自動(dòng)重置的事件調(diào)用ResetEvent函數(shù),因?yàn)橄到y(tǒng)會(huì)自動(dòng)對(duì)事件進(jìn)行重置。但是,Microsoft沒(méi)有為人工重置的事件定義成功等待的副作用,所以需要調(diào)用ResetEvent()。
五 線程同步之信號(hào)量(Semaphore)
信號(hào)量(Semaphore)內(nèi)核對(duì)象對(duì)線程的同步方式與前面幾種方法不同,它允許多個(gè)線程在同一時(shí)刻訪問(wèn)同一資源,但是需要限制在同一時(shí)刻訪問(wèn)此資源的最大線程數(shù)目。在用 CreateSemaphore()創(chuàng)建信號(hào)量時(shí)即要同時(shí)指出允許的最大資源計(jì)數(shù)和當(dāng)前可用資源計(jì)數(shù)。一般是將當(dāng)前可用資源計(jì)數(shù)設(shè)置為最大資源計(jì)數(shù),每增加一個(gè)線程對(duì)共享資源的訪問(wèn),當(dāng)前可用資源計(jì)數(shù)就會(huì)減1,只要當(dāng)前可用資源計(jì)數(shù)是大于0的,就可以發(fā)出信號(hào)量信號(hào)。但是當(dāng)前可用計(jì)數(shù)減小到0時(shí)則說(shuō)明當(dāng)前占用資源的線程數(shù)已經(jīng)達(dá)到了所允許的最大數(shù)目,不能在允許其他線程的進(jìn)入,此時(shí)的信號(hào)量信號(hào)將無(wú)法發(fā)出。線程在處理完共享資源后,應(yīng)在離開的同時(shí)通過(guò) ReleaseSemaphore()函數(shù)將當(dāng)前可用資源計(jì)數(shù)加1。在任何時(shí)候當(dāng)前可用資源計(jì)數(shù)決不可能大于最大資源計(jì)數(shù)。
使用信號(hào)量?jī)?nèi)核對(duì)象進(jìn)行線程同步主要會(huì)用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、 WaitForSingleObject()和WaitForMultipleObjects()等函數(shù)。
六 線程同步之其他
1)線程局部存儲(chǔ) (TLS),同一進(jìn)程中的所有線程共享相同的虛擬地址空間。不同的線程中的局部變量有不同的副本,但是static和globl變量是同一進(jìn)程中的所有線程共享的。使用TLS技術(shù)可以為static和globl的變量,根據(jù)當(dāng)前進(jìn)程的線程數(shù)量創(chuàng)建一個(gè)array,每個(gè)線程可以通過(guò)array的index來(lái)訪問(wèn)對(duì)應(yīng)的變量,這樣也就保證了static和global的變量為每一個(gè)線程都創(chuàng)建不同的副本。
2)互鎖函數(shù)的家族十分的龐大,例如InterlockedExchangeAdd()。。。,使用互鎖函數(shù)的優(yōu)點(diǎn)是:他的速度要比其他的CriticalSection,Mutex,Event,Semaphore快很多。
3)等待函數(shù),例如WaitForSingleObject 函數(shù)用來(lái)檢測(cè) hHandle 事件的信號(hào)狀態(tài),當(dāng)函數(shù)的執(zhí)行時(shí)間超過(guò) dwMilliseconds 就返回,但如果參數(shù) dwMilliseconds 為 INFINITE 時(shí)函數(shù)將直到相應(yīng)時(shí)間事件變成有信號(hào)狀態(tài)才返回,否則就一直等待下去,直到 WaitForSingleObject 有返回直才執(zhí)行后面的代碼。
六 《Windows核心編程(英文版·第5版)》
最好的windows多線程編程參考,更多更詳細(xì)請(qǐng)看原書。