线程基础知识——Windows核心编程学习手札系列之六
線程基礎(chǔ)知識(shí)
——Windows核心編程學(xué)習(xí)手札系列之六
線程與進(jìn)程一樣由兩部分構(gòu)成:一是線程的內(nèi)核對(duì)象,操作系統(tǒng)用它來(lái)對(duì)線程實(shí)施管理,也是系統(tǒng)用來(lái)存放線程統(tǒng)計(jì)信息的地方;二是線程堆棧,用于維護(hù)線程在執(zhí)行代碼時(shí)需要的所有函數(shù)參數(shù)和局部變量。進(jìn)程是靜止的,不執(zhí)行代碼,只是線程的容器,線程總是在某個(gè)進(jìn)程環(huán)境中創(chuàng)建的,且其整個(gè)壽命周期都在該進(jìn)程中,線程在進(jìn)程的地址空間中執(zhí)行代碼和操作數(shù)據(jù)。在單進(jìn)程中,存在多個(gè)線程運(yùn)行,這些線程共享進(jìn)程的地址空間,執(zhí)行相同代碼和操作相同數(shù)據(jù),還共享內(nèi)核對(duì)象句柄(句柄表依賴于進(jìn)程存在而非線程)。進(jìn)程使用的資源比較多,需要為進(jìn)程創(chuàng)建一個(gè)虛擬地址空間(需要系統(tǒng)很多資源);要保留眾多記錄(占用大量?jī)?nèi)存);文件如exe和dll要加載到地址空間(需要文件資源),相比下線程只需要一個(gè)內(nèi)核對(duì)象和一個(gè)堆棧,需要少量?jī)?nèi)存維護(hù)記錄即可。線程的開(kāi)銷比進(jìn)程少,在開(kāi)發(fā)中始終設(shè)法用線程來(lái)解決編程問(wèn)題是最優(yōu)選擇。
每個(gè)進(jìn)程在初始化時(shí),系統(tǒng)會(huì)為其創(chuàng)建一個(gè)主線程,該線程與C/C++運(yùn)行期庫(kù)的啟動(dòng)代碼一并開(kāi)始運(yùn)行,啟動(dòng)代碼調(diào)用進(jìn)入點(diǎn)函數(shù)(main/wmain/WinMain/wWinMian)并繼續(xù)運(yùn)行直到進(jìn)入點(diǎn)函數(shù)返回并且C/C++運(yùn)行期庫(kù)的啟動(dòng)代碼調(diào)用ExitProcess為止。每個(gè)線程都有進(jìn)入點(diǎn)函數(shù),線程從這個(gè)進(jìn)入點(diǎn)開(kāi)始運(yùn)行,主線程的進(jìn)入點(diǎn)函數(shù)是main/wmain/WinMain/wWinMain,如想要在進(jìn)程中創(chuàng)建輔助線程,也需要一個(gè)進(jìn)入點(diǎn)函數(shù),如同下面的代碼:
DWORD WINAPI ThreadFunc(PVOID pvParam){
?????? DWORD dwResult=0;
?????? ……
?????? return (dwResult);
}
線程函數(shù)可以執(zhí)行你所要執(zhí)行的任務(wù),線程函數(shù)達(dá)到結(jié)尾處且返回,線程終止運(yùn)行,堆棧的內(nèi)存被釋放,同時(shí)線程的內(nèi)核對(duì)象被遞減,如使用計(jì)數(shù)為零,其內(nèi)核對(duì)象被撤消。對(duì)線程函數(shù)應(yīng)該認(rèn)識(shí)到的是:1)主線程的進(jìn)入點(diǎn)函數(shù)名字必須是main/wmain/wWinMain/WinMain這四個(gè),其他線程函數(shù)可以使用任何名字,但不同線程之間的函數(shù)名必須不同,否則編譯器或鏈接程序會(huì)認(rèn)為單個(gè)函數(shù)創(chuàng)建了多個(gè)實(shí)現(xiàn)函數(shù);2)線程函數(shù)可以傳遞單個(gè)參數(shù),參數(shù)含義自定義而不由操作系統(tǒng)來(lái)定義;3)線程函數(shù)必須返回一個(gè)值,作為該線程的推出代碼;4)線程函數(shù)應(yīng)盡可能使用函數(shù)參數(shù)和局部變量,如使用靜態(tài)變量和全局變量,多個(gè)線程可以同時(shí)訪問(wèn)這些變量,可能破壞變量的內(nèi)容。
創(chuàng)建新的線程,可在正運(yùn)行的線程中調(diào)用CreateThread函數(shù)來(lái)實(shí)現(xiàn):
HANDLE CreateThread(
???????????????????? PSECURITY_ATTRIBUTES psa,
???????????????????? DWORD cbStack,
???????????????????? PTHREAD_START_ROUTINE pfnStartAddr,
???????????????????? PVOID pvParam,
???????????????????? DWORD fdwCreate,
??????????????????? PDWORD pdwThreadID);
當(dāng)CreateThread被調(diào)用時(shí),系統(tǒng)創(chuàng)建一個(gè)線程內(nèi)核對(duì)象,是操作系統(tǒng)用來(lái)管理線程的較小數(shù)據(jù)結(jié)構(gòu),是關(guān)于線程統(tǒng)計(jì)信息組成的小型數(shù)據(jù)結(jié)構(gòu)。系統(tǒng)從進(jìn)程的地址空間中分配內(nèi)存供線程的堆棧使用,新線程運(yùn)行的進(jìn)程環(huán)境與創(chuàng)建線程一樣,新線程可以訪問(wèn)進(jìn)程的內(nèi)核對(duì)象的所有句柄、所有內(nèi)存和在這個(gè)進(jìn)程中的其他線程的堆棧。CreateThread函數(shù)是Windows提供的用來(lái)創(chuàng)建線程,如在編寫(xiě)C/C++代碼,不應(yīng)調(diào)用該函數(shù),而應(yīng)該使用Visual C++運(yùn)行期庫(kù)函數(shù)_beginthreadex來(lái)創(chuàng)建新線程。一個(gè)原則就是盡量采用編譯器供應(yīng)商自己的創(chuàng)建線程函數(shù)來(lái)代替CreateThread的Windows函數(shù)。
函數(shù)CreateThread的第一個(gè)參數(shù)psa是指向SECURITY_ATTRIBUTES結(jié)構(gòu)的指針,如僅設(shè)置該線程的內(nèi)核對(duì)象為默認(rèn)安全屬性,可以直接傳遞NULL;如希望子進(jìn)程能夠繼承該線程對(duì)象的句柄,則需要設(shè)定一個(gè)SECURITY_ATTRIBUTES結(jié)構(gòu),它的bInheritHandle成員被初始化為TURE值。
函數(shù)CreateThread的第二個(gè)參數(shù)cbStack用于設(shè)定線程可以將多少地址空間用于自己的堆棧。當(dāng)CreateProcess啟動(dòng)一個(gè)進(jìn)程時(shí),在內(nèi)部調(diào)用CreateThread來(lái)對(duì)進(jìn)程的主線程進(jìn)行初始化。對(duì)于cbStack參數(shù)來(lái)說(shuō),CreateProcess使用存在在可執(zhí)行文件中的一個(gè)值,可使用鏈接程序的/STACK開(kāi)關(guān)來(lái)控制cbStack值:/STACK:[reverse][.commit],reserve參數(shù)用于設(shè)定系統(tǒng)應(yīng)該為線程堆棧保留的地址空間量,默認(rèn)值是1MB;commit參數(shù)用于設(shè)定開(kāi)始時(shí)應(yīng)該承諾用于堆棧保留區(qū)的物理存儲(chǔ)器的容量,默認(rèn)值是 1頁(yè)。當(dāng)線程中的代碼執(zhí)行時(shí),可能需要多個(gè)頁(yè)面的存儲(chǔ)器,當(dāng)線程溢出它的堆棧時(shí),就生成一個(gè)異常條件,系統(tǒng)抓取該異常條件,并將另一頁(yè)用于保留空間,使得線程的堆棧能夠根據(jù)需要?jiǎng)討B(tài)地?cái)U(kuò)大。當(dāng)調(diào)用CraeteThrad時(shí),如果cbStack傳遞的值不是0,就能使該函數(shù)將所有的存儲(chǔ)器保留并分配給線程的堆棧,由于所有的存儲(chǔ)器預(yù)先作了分配,因此可以確保線程擁有指定容量的可用堆棧存儲(chǔ)器。保留空間的容量可以在是/STACK鏈接設(shè)定的值也可以是cbStack的值,誰(shuí)大用誰(shuí)。分配的存儲(chǔ)器容量則與傳遞的cbStack一致,如將0傳遞給cbStack參數(shù),CreateThread 就保留一個(gè)區(qū)域,并且將鏈接程序嵌入exe文件的/STACk鏈接程序開(kāi)關(guān)信息指明的存儲(chǔ)器容量分配給線程堆棧。保留空間的容量用于為堆棧設(shè)置一個(gè)上限,這樣可以抓住代碼中的循環(huán)遞歸錯(cuò)誤,防止遞歸函數(shù)無(wú)限制地循環(huán)調(diào)用自己和創(chuàng)建新堆棧,從而消耗掉存儲(chǔ)資源。
函數(shù)CreateThread的第三個(gè)參數(shù)pfnStartAddr指明想要新線程執(zhí)行的線程函數(shù)地址。函數(shù)CreateThread的第四個(gè)參數(shù)pvParam作為將初始化值傳遞給線程函數(shù)的手段,既可以是數(shù)字值,也可以是指向包含其他信息的一個(gè)數(shù)據(jù)結(jié)構(gòu)指針。創(chuàng)建多個(gè)線程,使這些線程擁有和起始點(diǎn)相同的函數(shù)地址,如實(shí)現(xiàn)一個(gè)Web服務(wù)器,創(chuàng)建一個(gè)新線程處理每個(gè)客戶機(jī)的請(qǐng)求,每個(gè)線程都知道自己正在服務(wù)于哪個(gè)客戶端的請(qǐng)求,這需要在創(chuàng)建線程時(shí)傳遞不同的pvParam參數(shù)。
函數(shù)CreateThread的第五個(gè)參數(shù)fdwCreate可以設(shè)定用于控制創(chuàng)建線程的其他標(biāo)志,如果該值是0,那線程創(chuàng)建后立即進(jìn)行調(diào)度,如該值是CREATE_SUSPENDED,系統(tǒng)可以完整地創(chuàng)建線程并對(duì)它進(jìn)行初始化,但要暫停該線程的運(yùn)行,使其無(wú)法進(jìn)行調(diào)度。CREATE_SUSPENDED標(biāo)志使得應(yīng)用程序能夠在它有機(jī)會(huì)執(zhí)行任何代碼之前修改線程的某些屬性,由于這種必要性很少,所以通常不用。函數(shù)CreateThread的第六個(gè)參數(shù)pdwThreadID是DWORD類型的一個(gè)有效地址,CreateThread使用這個(gè)地址來(lái)存放系統(tǒng)分配給新線程的ID。
終止線程的運(yùn)行,方法有:1)線程函數(shù)返回(最優(yōu));2)調(diào)用ExitThread函數(shù),線程將自行撤消(最好不用);3)同一個(gè)進(jìn)程或另一個(gè)進(jìn)程中的線程調(diào)用TerminateThread函數(shù)(避免使用該方法);4)包含線程的進(jìn)程終止運(yùn)行(避免使用)。線程函數(shù)返回可確保所有線程資源被正確地清除:1)在線程函數(shù)中創(chuàng)建的所有C++對(duì)象均將通過(guò)它們的撤消函數(shù)正確地撤消;2)操作系統(tǒng)將正確地釋放線程堆棧使用的內(nèi)存;3)系統(tǒng)將線程的推出代碼(在線程的內(nèi)核對(duì)象中維護(hù))設(shè)置為線程函數(shù)的返回值;4)系統(tǒng)將遞減線程內(nèi)核對(duì)象的使用計(jì)數(shù)??梢宰尵€程強(qiáng)制終止運(yùn)行,通過(guò)調(diào)用VOID ExitThread(DWORD dwExitCode)函數(shù)來(lái)實(shí)現(xiàn),該函數(shù)將終止線程的運(yùn)行,并導(dǎo)致操作系統(tǒng)清除該線程使用的所有操作系統(tǒng)資源,但C++資源如C++類對(duì)象將不被撤消,基于這個(gè)原因,最好從線程函數(shù)返回,而不是通過(guò)調(diào)用ExitThread函數(shù)來(lái)返回。如創(chuàng)建線程一樣,應(yīng)盡量使用Visual C++運(yùn)行期庫(kù)函數(shù)_endthreadex來(lái)終止線程運(yùn)行。TerminateThread函數(shù)也可終止線程運(yùn)行:BOOL TerminateThread(HANDLE hThread,DOWRD dwExitCode),能夠撤消任何線程而ExitThread只能撤消調(diào)用的線程,其中hThread作為參數(shù)用于標(biāo)識(shí)被終止運(yùn)行線程的句柄。TerminateThread函數(shù)是異步運(yùn)行的函數(shù),告訴系統(tǒng)終止線程的運(yùn)行,但當(dāng)函數(shù)返回時(shí),不能保證線程被撤消,如果需要確切知道線程終止運(yùn)行,需調(diào)用WaitForSingleObject或者類似函數(shù),傳遞線程的句柄。當(dāng)線程終止運(yùn)行時(shí),會(huì)發(fā)生下列操作:1)線程擁有的所有用戶對(duì)象均被釋放,在Windows中大多數(shù)對(duì)象都是由包含創(chuàng)建這些對(duì)象的線程的進(jìn)程所擁有的,但是一個(gè)線程擁有兩個(gè)用戶對(duì)象,即窗口和掛鉤,當(dāng)線程終止運(yùn)行時(shí),系統(tǒng)會(huì)自動(dòng)撤消任何窗口,并且卸載線程創(chuàng)建的或安裝的任何掛鉤,其他對(duì)象只有在擁有線程的進(jìn)程終止運(yùn)行時(shí)才被撤消;2)線程的退出代碼從STILL_ACTIVE改為傳遞給ExitThread或TerminateThread的代碼;3)線程內(nèi)核對(duì)象狀態(tài)變?yōu)橐淹ㄖ?#xff1b;4)如果線程是進(jìn)程中的最后一個(gè)活動(dòng)線程,系統(tǒng)也將進(jìn)程視為已終止運(yùn)行;5)線程內(nèi)核對(duì)象的使用計(jì)數(shù)遞減1。一旦線程不再運(yùn)行,系統(tǒng)中就沒(méi)有別的線程能夠處理該線程的句柄,然而別的線程可以用GetExitCodeThread來(lái)檢查由hThread標(biāo)識(shí)的線程是否已經(jīng)終止運(yùn)行,如果它已經(jīng)終止運(yùn)行則確定它的退出代碼:BOOL GetExitCodeThread(HANDLE hThread,PDWORD pdwExitCode),退出代碼的值在pdwExitCode指向的DWORD中返回,如果調(diào)用GetExitCodeThread函數(shù)時(shí)線程還未終止運(yùn)行,該函數(shù)就用STILL_ACTIVE標(biāo)識(shí)符(定義為0x103)填入DWORD,如果函數(shù)運(yùn)行成功便返回TURE值。
????????????????????????????????????????? ?如非? 2008-12-16
?
總結(jié)
以上是生活随笔為你收集整理的线程基础知识——Windows核心编程学习手札系列之六的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 作业——Windows核心编程学习手札系
- 下一篇: 泛架构之于外包IT工程