日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Win32多线程编程(2) — 线程控制

發(fā)布時(shí)間:2024/4/11 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Win32多线程编程(2) — 线程控制 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Win32線(xiàn)程控制只有是圍繞線(xiàn)程這一內(nèi)核對(duì)象的創(chuàng)建、掛起、恢復(fù)、終結(jié)以及通信等操作,這些操作都依賴(lài)于Win32操作系統(tǒng)提供的一組API和具體編譯器的C運(yùn)行時(shí)庫(kù)函數(shù)。本篇圍繞這些操作接口介紹在Windows系統(tǒng)下的多線(xiàn)程編程要點(diǎn),后續(xù)將進(jìn)一步涉及多線(xiàn)程通信的同步互斥等議題。

?

1.線(xiàn)程的創(chuàng)建(CreateThread)

每個(gè)線(xiàn)程必須擁有一個(gè)進(jìn)入點(diǎn)函數(shù),線(xiàn)程從這個(gè)進(jìn)入點(diǎn)開(kāi)始運(yùn)行。主線(xiàn)程的進(jìn)入點(diǎn)是main/WinMain函數(shù),如果想在進(jìn)程中創(chuàng)建一個(gè)輔助線(xiàn)程,則必須為該輔助線(xiàn)程指定一個(gè)進(jìn)入點(diǎn)函數(shù),這個(gè)函數(shù)稱(chēng)為線(xiàn)程函數(shù)

線(xiàn)程函數(shù)的定義如下:

typedef?DWORD?(WINAPI*?ThreadProc)(LPVOID?lpParam);?//?線(xiàn)程函數(shù)名稱(chēng)ThreadProc可以是任意的

WINAPI是一個(gè)宏名,在?windef.h文件中有如下的聲明。

#define?WINAPI?__stdcall

__stdcall?是Windows標(biāo)準(zhǔn)?C/C++函數(shù)的調(diào)用方法。從底層上說(shuō),使用這種調(diào)用方法參數(shù)的進(jìn)棧順序和標(biāo)準(zhǔn)?C調(diào)用(__cdecl?方法)是一樣的,都是從右到左,但是__stdcall?采用自動(dòng)清棧的方式,而__cdecl?采用的是手工清棧方式。

Windows?規(guī)定,凡是由它來(lái)負(fù)責(zé)調(diào)用的函數(shù)都必須定義為_(kāi)_stdcall?類(lèi)型。

ThreadProc是一個(gè)回調(diào)函數(shù),即由Windows系統(tǒng)來(lái)負(fù)責(zé)調(diào)用的函數(shù),所以此函數(shù)應(yīng)定義為_(kāi)_stdcall類(lèi)型。注意,如果沒(méi)有顯式說(shuō)明的話(huà),函數(shù)的調(diào)用方法是__cdecl。

Windows創(chuàng)建新線(xiàn)程的API是CreateThread,由該函數(shù)創(chuàng)建的線(xiàn)程將在調(diào)用者的虛擬地址空間內(nèi)執(zhí)行。函數(shù)原型如下:

// The CreateThread function creates a thread to execute within the virtual address space of the calling process.

// To create a thread that runs in the virtual address space of another process, use the CreateRemoteThread function.

HANDLE?CreateThread(

??????????????????LPSECURITY_ATTRIBUTES?lpThreadAttributes,?// SD

??????????????????SIZE_T?dwStackSize,???????????????????????// initial stack size

??????????????????LPTHREAD_START_ROUTINE?lpStartAddress,????// thread function

??????????????????LPVOID?lpParameter,???????????????????????// thread argument

??????????????????DWORD?dwCreationFlags,????????????????????// creation option

??????????????????LPDWORD?lpThreadId????????????????????????// thread identifier

??????????????????);

參數(shù)一為線(xiàn)程的安全屬性,一般設(shè)為NULL,表示使用默認(rèn)安全屬性。

參數(shù)二為線(xiàn)程堆棧大小,一般設(shè)為NULL,表示使用默認(rèn)堆棧大小,對(duì)應(yīng)VC的/STACK:鏈接器選項(xiàng)。VC6默認(rèn)的堆棧大小為1M,可通過(guò)“Project SettingsàLinkàStack allocations”設(shè)置堆棧大小;VC2005中,可在“項(xiàng)目屬性à配置屬性à鏈接器à系統(tǒng)”中設(shè)置堆棧大小。

參數(shù)三為線(xiàn)程函數(shù)的地址,傳遞函數(shù)指針或函數(shù)名ThreadProc

參數(shù)四為傳遞給線(xiàn)程函數(shù)的參數(shù),即ThreadProc的參數(shù)。其為L(zhǎng)PVOID類(lèi)型,對(duì)復(fù)雜的參數(shù)采用結(jié)構(gòu)體或類(lèi)按址傳遞。

參數(shù)五為線(xiàn)程創(chuàng)建參數(shù),例如線(xiàn)程創(chuàng)建后是否立即啟動(dòng)的開(kāi)關(guān)選項(xiàng)。

參數(shù)六為內(nèi)核給新創(chuàng)建的線(xiàn)程分配的線(xiàn)程ID號(hào),為輸出參數(shù)。

用戶(hù)編寫(xiě)多線(xiàn)程程序時(shí),一般關(guān)注參數(shù)三和參數(shù)四足矣,其他可采用默認(rèn)參數(shù)。

此函數(shù)執(zhí)行成功后,將返回新建線(xiàn)程的線(xiàn)程句柄。lpStartAddress參數(shù)指定了線(xiàn)程函數(shù)的地址,新建線(xiàn)程將從此地址開(kāi)始執(zhí)行,直到?return?語(yǔ)句返回,線(xiàn)程運(yùn)行結(jié)束,把控制權(quán)交給操作系統(tǒng)。

線(xiàn)程內(nèi)核對(duì)象(kthread)

線(xiàn)程內(nèi)核對(duì)象就是一個(gè)包含了線(xiàn)程狀態(tài)信息的數(shù)據(jù)結(jié)構(gòu)。每一次對(duì)?CreateThread?函數(shù)的成功調(diào)用,系統(tǒng)都會(huì)在內(nèi)部為新的線(xiàn)程分配一個(gè)內(nèi)核對(duì)象。系統(tǒng)提供的管理線(xiàn)程的函數(shù)其實(shí)就是依靠訪(fǎng)問(wèn)線(xiàn)程內(nèi)核對(duì)象來(lái)實(shí)現(xiàn)管理的。在WinDbg中,可通過(guò)lkd> dt nt!_kthread查看線(xiàn)程內(nèi)核對(duì)象數(shù)據(jù)結(jié)構(gòu),這里涉及到線(xiàn)程上下文(Context)、使用計(jì)數(shù)(Usage Count)和暫停計(jì)數(shù)(Suspend Count)等重要概念。

(1)線(xiàn)程上下文

線(xiàn)程的上下文本質(zhì)上是一組處理器的寄存器,有正在執(zhí)行程序中的指針及堆棧指針。上下文及其轉(zhuǎn)換的過(guò)程根據(jù)處理器的結(jié)構(gòu)不同會(huì)有所不同,參考《線(xiàn)程的數(shù)據(jù)結(jié)構(gòu)》。在WinDbg中,可以通過(guò)lkd> dt nt!_context命令來(lái)觀察上下文的數(shù)據(jù)結(jié)構(gòu)。<WINNT.H>中定義了_CONTEXT結(jié)構(gòu)。

大約每經(jīng)?20ms,Windows?查看一次當(dāng)前存在的所有線(xiàn)程內(nèi)核對(duì)象。在這些對(duì)象中,只有一少部分是可調(diào)度的(沒(méi)有處于暫停狀態(tài)),Windows?選擇其中的一個(gè)內(nèi)核對(duì)象,將它的CONTEXT(上下文)裝入?CPU的寄存器,這一過(guò)程稱(chēng)為上下文切換。

用戶(hù)可調(diào)用GetThreadContext查看當(dāng)前線(xiàn)程的用戶(hù)模式的上下文信息;調(diào)用SetThreadContext改變線(xiàn)程上下文,待下次調(diào)度進(jìn)CPU時(shí)生效。其中,ContextFlags參數(shù)通過(guò)異或掩碼指定欲查看的寄存器。_KTHREAD::ContextSwitches為線(xiàn)程已切換的次數(shù)。

(2)使用計(jì)數(shù)

Usage Count成員記錄了線(xiàn)程內(nèi)核對(duì)象的使用計(jì)數(shù),這個(gè)計(jì)數(shù)說(shuō)明了此內(nèi)核對(duì)象被打開(kāi)的次數(shù)。線(xiàn)程內(nèi)核對(duì)象的存在與?Usage Count?的值息息相關(guān),當(dāng)這個(gè)值是0?的時(shí)候,系統(tǒng)就認(rèn)為已經(jīng)沒(méi)有任何進(jìn)程在引用此內(nèi)核對(duì)象了,于是線(xiàn)程內(nèi)核對(duì)象就要從內(nèi)存中撤銷(xiāo)。

只要線(xiàn)程沒(méi)有結(jié)束運(yùn)行,Usage??Count?的值就至少為?1。在創(chuàng)建一個(gè)新的線(xiàn)程時(shí),CreateThread?函數(shù)返回了線(xiàn)程內(nèi)核對(duì)象的句柄,相當(dāng)于打開(kāi)一次新創(chuàng)建的內(nèi)核對(duì)象,這也會(huì)促使?Usage Count?的值加1。所以創(chuàng)建一個(gè)新的線(xiàn)程后,初始狀態(tài)下?Usage Count?的值是2。之后,只要有進(jìn)程打開(kāi)此內(nèi)核對(duì)象,就會(huì)使Usage Count的值加1。比如當(dāng)有一個(gè)進(jìn)程調(diào)用OpenThread函數(shù)打開(kāi)這個(gè)線(xiàn)程內(nèi)核對(duì)象后,Usage Count?的值會(huì)再次加?1。

// The OpenThread function opens an existing thread object.

HANDLE?OpenThread(

????????????????DWORD?dwDesiredAccess,??// access right

????????????????BOOL?bInheritHandle,????// handle inheritance option

????????????????DWORD?dwThreadId????????// thread identifier

????????????????);

由于對(duì)這個(gè)函數(shù)的調(diào)用會(huì)使?Usage Count?的值加1,所以在使用完它們返回的句柄后一定要調(diào)用?CloseHandle?函數(shù)進(jìn)行關(guān)閉。關(guān)閉內(nèi)核對(duì)象句柄的操作就會(huì)使?Usage Count?的值減?1。

還有一些函數(shù)僅僅返回內(nèi)核對(duì)象的偽句柄,并不會(huì)創(chuàng)建新的句柄,當(dāng)然也就不會(huì)影響Usage Count?的值。如果對(duì)這些偽句柄調(diào)用?CloseHandle?函數(shù),那么?CloseHandle?就會(huì)忽略對(duì)自己的調(diào)用并返回?FALSE。對(duì)進(jìn)程和線(xiàn)程來(lái)說(shuō),這些函數(shù)有:

// The GetCurrentProcess function retrieves a pseudo handle for the current process.

HANDLE?GetCurrentProcess(VOID);

// The GetCurrentThread function retrieves a pseudo handle for the current thread.

HANDLE?GetCurrentThread(VOID);

前面提到,新創(chuàng)建的線(xiàn)程在初始狀態(tài)下?Usage??Count?的值是?2。此時(shí)如果立即調(diào)用CloseHandle?函數(shù)來(lái)關(guān)閉CreateThread返回的句柄的話(huà),Usage Count?的值將減為?1,但新創(chuàng)建的線(xiàn)程是不會(huì)被終止的。待線(xiàn)程函數(shù)返回,系統(tǒng)會(huì)使?Usage Count?的值由1減為0,線(xiàn)程的生命周期到此為止,系統(tǒng)將撤銷(xiāo)此線(xiàn)程內(nèi)核對(duì)象,釋放其所占內(nèi)存。

如果不關(guān)閉句柄的話(huà),Usage Count?的值將永遠(yuǎn)不會(huì)是?0,系統(tǒng)將永遠(yuǎn)不會(huì)撤銷(xiāo)它占用的內(nèi)存,這就會(huì)造成內(nèi)存泄漏(當(dāng)然,線(xiàn)程所在的進(jìn)程結(jié)束后,該進(jìn)程占用的所有資源都要釋放)。

(3)暫停計(jì)數(shù)

暫停計(jì)數(shù)參考下面的多線(xiàn)程狀態(tài)控制。

(4)主輔線(xiàn)程的執(zhí)行同步

一般主線(xiàn)程應(yīng)該后于輔助線(xiàn)程退出,如果主線(xiàn)程先退出,輔助線(xiàn)程尚在執(zhí)行,將會(huì)出現(xiàn)意想不到的結(jié)果,因此主線(xiàn)程必須對(duì)輔助線(xiàn)程具有完全的控制權(quán)。

一個(gè)可執(zhí)行對(duì)象有兩種狀態(tài),未受信(nonsignaled)和受信(signaled)狀態(tài)。線(xiàn)程內(nèi)核對(duì)象只有當(dāng)線(xiàn)程過(guò)程運(yùn)行結(jié)束時(shí)才達(dá)到受信狀態(tài)。可調(diào)用WaitForSingleObject/WaitForMultipleObjects函數(shù)在線(xiàn)程內(nèi)核對(duì)象(HANDLE)上等待,以便主輔線(xiàn)程同步。

?

2.線(xiàn)程的狀態(tài)控制(SuspendThread/ResumeThread、Sleep)

線(xiàn)程在創(chuàng)建后和終止前之間的狀態(tài),用戶(hù)可感知或控制的狀態(tài)主要有運(yùn)行和暫停(中斷)兩種,對(duì)應(yīng)的操作為掛起(Suspend)和恢復(fù)(Resume)。

線(xiàn)程內(nèi)核對(duì)象中的Suspend Count(_KTHREAD::SuspendCount)用于指明線(xiàn)程的暫停計(jì)數(shù)。

當(dāng)調(diào)用CreateProcess(創(chuàng)建進(jìn)程的主線(xiàn)程)或CreateThread函數(shù)時(shí),線(xiàn)程的內(nèi)核對(duì)象被創(chuàng)建了,它的暫停計(jì)數(shù)被初始化為1(即出于暫停狀態(tài)),這可以阻止新創(chuàng)建的線(xiàn)程被立即調(diào)度進(jìn)CPU中。因?yàn)榫€(xiàn)程初始化需要時(shí)間,當(dāng)線(xiàn)程完全初始化好了之后,CreateProcessCreateThread檢查dwCreationFlags參數(shù)是否傳遞了CREATE_SUSPEND標(biāo)志,如果傳遞了,這些函數(shù)就返回,同時(shí)新線(xiàn)程處于暫停狀態(tài)。如果尚未傳遞該標(biāo)志,那么線(xiàn)程的暫停計(jì)數(shù)將被遞減為0。當(dāng)線(xiàn)程的暫停計(jì)數(shù)是?0的時(shí)候,該線(xiàn)程就進(jìn)入可調(diào)度狀態(tài)。

創(chuàng)建線(xiàn)程的時(shí)候,若指定CREATE_SUSPEND標(biāo)志,則用戶(hù)有機(jī)會(huì)在線(xiàn)程執(zhí)行任何代碼之前改變線(xiàn)程的運(yùn)行環(huán)境(如后面討論的優(yōu)先級(jí))。然后,需要調(diào)用ResumeThread函數(shù),減少線(xiàn)程的暫停計(jì)數(shù)至0,使線(xiàn)程恢復(fù)運(yùn)行,進(jìn)入可調(diào)度狀態(tài)。

// The ResumeThread function?decrements?a thread's suspend count. When the suspend count is decremented to zero, the execution of the thread is resumed.

DWORD?ResumeThread(

?????????????????HANDLE?hThread???// handle to thread

?????????????????);

后續(xù)可調(diào)用SuspendThread函數(shù)來(lái)暫停一個(gè)線(xiàn)程的運(yùn)行,因?yàn)樵揂PI傳遞的是句柄,故該函數(shù)可跨進(jìn)程調(diào)用,即在一個(gè)線(xiàn)程中暫停另一個(gè)線(xiàn)程。與ResumeThread相反,SuspendThread函數(shù)的調(diào)用會(huì)增加線(xiàn)程的暫停計(jì)數(shù)。

// The SuspendThread function suspends the specified thread.

DWORD?SuspendThread(

??????????????????HANDLE?hThread???// handle to thread

??????????????????);

只有當(dāng)線(xiàn)程的暫停計(jì)數(shù)是?0的時(shí)候,線(xiàn)程才能進(jìn)入可調(diào)度狀態(tài)。因此,必須注意SuspendThread/ResumeThread函數(shù)調(diào)用次數(shù)的匹配,以便正確控制。

SuspendThread/ResumeThread函數(shù)對(duì)于線(xiàn)程狀態(tài)的控制具有很強(qiáng)的針對(duì)性,另外一種簡(jiǎn)單的替代方案是調(diào)用Sleep函數(shù),讓調(diào)用線(xiàn)程睡一會(huì)兒,給其他的線(xiàn)程一個(gè)調(diào)度機(jī)會(huì),從而提供一種線(xiàn)程切換緩沖機(jī)制。

// The Sleep function suspends the execution of the current thread for the specified interval.

// To enter an alertable wait state, use the SleepEx function.

VOID?Sleep(

??????????DWORD?dwMilliseconds???// sleep time

??????????);

Sleep函數(shù)在線(xiàn)程的while(1)?死循環(huán)中非常實(shí)用,因?yàn)橐粋€(gè)線(xiàn)程如果while(1)輪回,則CPU使用率一般會(huì)飆升,置系統(tǒng)與卡死狀態(tài)。特殊地,Sleep(0)表示調(diào)用線(xiàn)程主動(dòng)放棄時(shí)間片的剩余部分,它強(qiáng)制系統(tǒng)調(diào)度其他線(xiàn)程。但是,系統(tǒng)有可能重新調(diào)度剛剛調(diào)用了Sleep的那個(gè)線(xiàn)程。?由于該調(diào)用本身占用時(shí)鐘周期,也會(huì)讓當(dāng)前線(xiàn)程放棄時(shí)間片,交出控制權(quán),從而使別的線(xiàn)程有機(jī)會(huì)執(zhí)行。當(dāng)然,可直接調(diào)用SwitchToThread()執(zhí)行線(xiàn)程切換。

?

3.線(xiàn)程的優(yōu)先級(jí)控制(GetThreadPriority/SetThreadPriority)

線(xiàn)程的狀態(tài)轉(zhuǎn)換、優(yōu)先級(jí)及調(diào)度方案,請(qǐng)參考《線(xiàn)程的調(diào)度》。

對(duì)于一個(gè)線(xiàn)程,我們可以調(diào)用GetThreadPriority函數(shù)來(lái)獲取其優(yōu)先級(jí)。

// The GetThreadPriority function retrieves the priority value for the specified thread. This value, together with the priority class of the thread's process, determines the thread's base-priority level.

int?GetThreadPriority(

????????????????????HANDLE?hThread???// handle to thread

????????????????????);

系統(tǒng)可以動(dòng)態(tài)地調(diào)整線(xiàn)程的優(yōu)先級(jí),當(dāng)系統(tǒng)希望這個(gè)線(xiàn)程處理窗口消息、I/O調(diào)用或是系統(tǒng)發(fā)現(xiàn)3-4秒內(nèi)這個(gè)線(xiàn)程一直迫切地需要一個(gè)時(shí)間片時(shí),它會(huì)將這個(gè)線(xiàn)程的優(yōu)先級(jí)調(diào)整為15并可以運(yùn)行雙倍的時(shí)間片。對(duì)線(xiàn)程優(yōu)先級(jí)的動(dòng)態(tài)調(diào)整是通過(guò)調(diào)用SetThreadPriority函數(shù)實(shí)現(xiàn)的。

// The SetThreadPriority function sets the priority value for the specified thread. This value, together with the priority class of the thread's process, determines the thread's base priority level.

BOOL?SetThreadPriority(

?????????????????????HANDLE?hThread,?// handle to the thread

?????????????????????int?nPriority???// thread priority level

?????????????????????);

????對(duì)于一般的應(yīng)用程序開(kāi)發(fā),按照常規(guī)的優(yōu)先級(jí)配置即可,很少涉及優(yōu)先級(jí)的操作。

?

4.線(xiàn)程的停止(ExitThread/TerminateThread)

線(xiàn)程過(guò)程的結(jié)束主要有兩種情形,即自然終止和人為中止。

自然終止,主要是指線(xiàn)程函數(shù)自然返回的情況,是壽終正寢的圓寂。人為中止是通過(guò)暴力手段將尚未完成使命的線(xiàn)程謀殺,是死于非命的夭折。

線(xiàn)程自然終止時(shí),會(huì)發(fā)生下列事件:

l??在線(xiàn)程函數(shù)中創(chuàng)建的所有?C++對(duì)象將通過(guò)它們各自的析構(gòu)函數(shù)被正確地銷(xiāo)毀。

l??該線(xiàn)程使用的堆棧將被釋放。

l??系統(tǒng)將線(xiàn)程內(nèi)核對(duì)象中?Exit Code(退出代碼)的值由?STILL_ACTIVE?設(shè)置為線(xiàn)程函數(shù)的返回值。

l??系統(tǒng)將遞減線(xiàn)程內(nèi)核對(duì)象中?Usage Code(使用計(jì)數(shù))的值。

線(xiàn)程結(jié)束后的退出代碼可以被其他線(xiàn)程用GetExitCodeThread函數(shù)檢測(cè)到,所以可以當(dāng)做自定義的返回值來(lái)表示線(xiàn)程的執(zhí)行結(jié)果。

人為中止主要有兩種手段:

(1)調(diào)用ExitThread(CRT中的exit)結(jié)束當(dāng)前線(xiàn)程(調(diào)用線(xiàn)程);

// The ExitThread function ends a thread.

VOID?ExitThread(

??????????????DWORD?dwExitCode???// exit code for this thread

??????????????);

ExitThread?函數(shù)會(huì)中止當(dāng)前線(xiàn)程的運(yùn)行,促使系統(tǒng)釋放掉所有此線(xiàn)程使用的資源。但是,線(xiàn)程函數(shù)中申請(qǐng)的C++資源,典型的如C++類(lèi)(class)卻不能得到正確地清除。這是因?yàn)镃++對(duì)象的析構(gòu)函數(shù)是通過(guò)atexit掛接到線(xiàn)程中,在exit退出時(shí)(main/WinMain或線(xiàn)程過(guò)程返回之后)調(diào)用。若線(xiàn)程過(guò)程被終止,則CRT越過(guò)atexit,從而使C++資源不能正確釋放。所以,結(jié)束線(xiàn)程最好的方法還是讓線(xiàn)程自然返回。

(2)調(diào)用TerminateThread進(jìn)行跨線(xiàn)程中止。

// The TerminateThread function terminates a thread.

BOOL?TerminateThread(

???????????????????HANDLE?hThread,????// handle to thread

???????????????????DWORD?dwExitCode???// exit code

???????????????????);

因?yàn)樵揂PI傳遞的是句柄,故該函數(shù)可跨線(xiàn)程調(diào)用,即在一個(gè)線(xiàn)程中中止另一個(gè)線(xiàn)程。

這是一個(gè)被強(qiáng)烈建議避免使用的函數(shù),因?yàn)橐坏﹫?zhí)行這個(gè)函數(shù),程序無(wú)法預(yù)測(cè)目標(biāo)線(xiàn)程會(huì)在何處被中止,其結(jié)果就是目標(biāo)線(xiàn)程可能根本沒(méi)有機(jī)會(huì)來(lái)做清除工作,如線(xiàn)程中打開(kāi)的文件和申請(qǐng)的內(nèi)存都不會(huì)被釋放。另外,使用TerminateThread?函數(shù)中止線(xiàn)程的時(shí)候,系統(tǒng)不會(huì)釋放線(xiàn)程使用的堆棧。所以建議讀者在編程的時(shí)候盡量讓線(xiàn)程自己退出,如果主線(xiàn)程要求某個(gè)線(xiàn)程結(jié)束,可以通過(guò)各種方法通知線(xiàn)程,線(xiàn)程收到通知后自行退出。只有在迫不得已的情況下,才使用?TerminateThread?函數(shù)終止線(xiàn)程。

無(wú)論是自然終止還是人為中止,它們都將使使用計(jì)數(shù)減1。此時(shí),我們通過(guò)調(diào)用GetExitCodeThread函數(shù)來(lái)獲取線(xiàn)程函數(shù)的返回值。最后,必須調(diào)用CloseHandle關(guān)閉線(xiàn)程句柄,使使用計(jì)數(shù)再減1。至此,該線(xiàn)程內(nèi)核對(duì)象結(jié)束生命的旅程。

總之,始終應(yīng)該讓線(xiàn)程正常退出,即使它的線(xiàn)程函數(shù)自然返回。通知線(xiàn)程退出的方法很多,如設(shè)置全局變量、使用事件對(duì)象等,這涉及到下一節(jié)線(xiàn)程間的通信問(wèn)題。

?

5.用CRT的_beginthreadex代替操作系統(tǒng)的CreateThread

(1)為IDE選擇正確的RTL(Run Time Library)

在VC6中,“Project Settings?à?C/C++?à?Category(Code Generation)à?Use run-time library”共有六個(gè)選項(xiàng)供選擇:

/ML:Single-Threaded*

/MLd:Debug Single-Threaded

/MT:Multithreaded

/MTd:Debug Multithreaded

/MD:Multithreaded DLL

/MDd:Debug Multithreaded DLL

其中/ML[d]和/MT[d]主要針對(duì)常規(guī)多線(xiàn)程應(yīng)用程序的RTL版本選擇,/MD[d]主要針對(duì)多線(xiàn)程LIB或DLL工程中的RTL版本選擇。

VC6常規(guī)工程的默認(rèn)選項(xiàng)為/ML[d],MFC工程的默認(rèn)選項(xiàng)為/MD[d]。對(duì)于需要多線(xiàn)程支持的工程中,如果不設(shè)定/MT或/MD選項(xiàng),則可能出現(xiàn)“error LNK2001: unresolved external symbol __beginthreadex”或“error C2065: '_beginthreadex' : undeclared identifier”。

在VC2005中,“項(xiàng)目屬性?à?配置屬性?à?C/C++??à?代碼生成”中提供了四種選擇,

多線(xiàn)程(/MT

多線(xiàn)程調(diào)試(/MTd

多線(xiàn)程DLL(/MD

多線(xiàn)程調(diào)試DLL(/MDd

VC2005工程默認(rèn)選項(xiàng)為/MD[d]。

不同的鏈接選項(xiàng),鏈接器將選擇不同的鏈接庫(kù)進(jìn)行鏈接。參考《C Run-Time Libraries (CRT)》和《/MD, /ML, /MT, /LD???(Use Run-Time Library)》。

(2)用C運(yùn)行時(shí)庫(kù)的_beginthreadex代替操作系統(tǒng)的CreateThread來(lái)創(chuàng)建線(xiàn)程

在實(shí)際的開(kāi)發(fā)過(guò)程中,一般不直接使用Windows系統(tǒng)提供的CreateThread函數(shù)創(chuàng)建線(xiàn)程,而是使用?C/C++運(yùn)行期函數(shù)_beginthread(ex)/_endthread(ex)

使用_beginthread無(wú)法創(chuàng)建帶有安全屬性的新線(xiàn)程,無(wú)法創(chuàng)建暫停的線(xiàn)程,也無(wú)法獲得線(xiàn)程ID。_endthread的情況類(lèi)似,它不帶參數(shù),這意味這線(xiàn)程的退出代碼必須硬編碼為0。一般不調(diào)用_beginthread/_endthread,而是調(diào)用其擴(kuò)展版本_beginthreadex/_endthreadex

事實(shí)上,C/C++運(yùn)行期庫(kù)提供CreateThread加強(qiáng)版的_beginthreadex,是為了多線(xiàn)程同步的需要。在早期的單線(xiàn)程C運(yùn)行庫(kù)中有許多的全局變量,如errno、strerror等,它們可以用來(lái)表示線(xiàn)程當(dāng)前的一些狀態(tài)。

但是在多線(xiàn)程程序設(shè)計(jì)中,每個(gè)線(xiàn)程必須有惟一的狀態(tài),否則這些變量記錄的信息就不會(huì)準(zhǔn)確了。比如,全局變量errno?用于表示調(diào)用運(yùn)行期函數(shù)失敗后的錯(cuò)誤代碼。如果所有線(xiàn)程共享一個(gè)errno?的話(huà),在一個(gè)線(xiàn)程產(chǎn)生的錯(cuò)誤代碼就會(huì)影響到另一個(gè)線(xiàn)程。為了解決這個(gè)問(wèn)題,每個(gè)線(xiàn)程都需要有自己的errno?變量。

要想使運(yùn)行期為每個(gè)線(xiàn)程都設(shè)置狀態(tài)變量,必須在創(chuàng)建線(xiàn)程的時(shí)候調(diào)用運(yùn)行期提供的_beginthreadex,讓運(yùn)行期設(shè)置了相關(guān)變量后再去調(diào)用Windows系統(tǒng)提供的?CreateThread函數(shù)。

_beginthreadex的參數(shù)與CreateThread函數(shù)對(duì)應(yīng),函數(shù)的參數(shù)和數(shù)據(jù)類(lèi)型都是C Run-time Library中的類(lèi)型,在使用時(shí)候需要強(qiáng)制類(lèi)型轉(zhuǎn)換。

// /Microsoft Visual Studio/VC98/CRT/SRC/THREADEX.C

unsigned long?__cdecl?_beginthreadex(

????????void *security,

????????unsigned?stacksize,

????????unsigned (__stdcall*?initialcode)(void*),

????????void *?argument,

????????unsigned?createflag,

????????unsigned *thrdaddr)

{

_ptiddata?ptd;??????????????????/* pointer to per-thread data */

// ……

ptd->_initaddr?= (void *)initialcode;

ptd->_initarg?=?argument;

ptd->_thandle?= (unsigned long)(-1L);

// ……

CreateThread(security,

???????stacksize,

???????_threadstartex,

???????(LPVOID)ptd,??// pointer to per-thread data(_ptiddata?ptd)

???????createflag,

???????thrdaddr));

????// ……

}

線(xiàn)程啟動(dòng)函數(shù)為_threadstartex,其參數(shù)為線(xiàn)程局部存儲(chǔ)(TLS)結(jié)構(gòu)_ptiddata?ptd,其中包含了線(xiàn)程函數(shù)ptd->_initaddr和線(xiàn)程參數(shù)ptd->_initarg。關(guān)于線(xiàn)程局部存儲(chǔ)(TLS),參考后續(xù)議題。實(shí)際上_ptiddata結(jié)構(gòu)中定義了_terrno_token_errmsg等單線(xiàn)程全局變量。

struct?_tiddata

{

????unsigned long???_tid;???????????/* thread ID */

????unsigned long???_thandle;???????/* thread handle */

???

????int?????_terrno;????????????????/* errno value */

unsigned long???_holdrand;??????/* rand() seed value */

char?*??????_token;?????????????/* ptr to strtok() token */

?

char?*??????_errmsg;????????????/* ptr to strerror()/_strerror() buff */

?

????void?*??????_initaddr;??????????/* initial user thread address */

void?*??????_initarg;???????????/* initial user thread argument */

}

typedef struct?_tiddata?*?_ptiddata;

// _threadstartex() - New thread begins here

static unsigned long?WINAPI?_threadstartex(void *ptd)

{

// ……

// Call fp initialization, if necessary

if (?_FPmtinit?!=?NULL?)

(*_FPmtinit)();

// ……

_endthreadex(((unsigned (WINAPI?*)(void*))(((_ptiddata)ptd)->_initaddr))(((_ptiddata)ptd)->_initarg));

}

線(xiàn)程啟動(dòng)函數(shù)_threadstartex中,進(jìn)行相關(guān)的初始化(_FPmtinit)后,開(kāi)始調(diào)用ptd->_initaddr(ptd->_initarg)執(zhí)行真正的線(xiàn)程過(guò)程。傳遞線(xiàn)程過(guò)程返回碼調(diào)用_endthreadex函數(shù)。

// _endthreadex() - Terminate the calling thread

void?__cdecl?_endthreadex(unsigned?retcode)

_endthreadex函數(shù)釋放線(xiàn)程局部存儲(chǔ)(TLS)數(shù)據(jù)_ptiddata?ptd,調(diào)用ExitThread結(jié)束當(dāng)前線(xiàn)程的運(yùn)行。故這里有了不調(diào)用ExitThread的另一個(gè)理由:即它會(huì)阻止線(xiàn)程的_ptiddata內(nèi)存的釋放。故建議使用_endthreadex替代ExitThread調(diào)用,當(dāng)然,這也是不應(yīng)該提倡的做法。

參考《_beginthreadex和CreateThread》。

總結(jié)

以上是生活随笔為你收集整理的Win32多线程编程(2) — 线程控制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。