為什么要使用線程池:
創(chuàng)建多線程應(yīng)用程序是非常困難的。需要會(huì)面臨兩個(gè)大問(wèn)題。
一個(gè)是要對(duì)線程的創(chuàng)建和撤消進(jìn)行管理,另一個(gè)是要對(duì)線程對(duì)資源的訪問(wèn)實(shí)施同步 。
線程池(thread pool),允許有多個(gè)線程同時(shí)存在,并發(fā)執(zhí)行,并且這些線程受到統(tǒng)一管理。
在Windows Vista中,提供了全新的線程池機(jī)制,一般這些線程池中的線程的創(chuàng)建的銷(xiāo)毀是由操作系統(tǒng)自動(dòng)完成的。
Windows Vista 中重新設(shè)計(jì)了線程池,提供了一組新的線程池API。因此,本篇討論的僅僅在Windows Vista系統(tǒng),或其以上的Windows版本中有效。
當(dāng)一個(gè)進(jìn)程創(chuàng)建之后,它并不與線程池關(guān)聯(lián)。一旦新的線程池API函數(shù)被呼叫之后,系統(tǒng)就為該進(jìn)程創(chuàng)建內(nèi)核資源,并且有些資源直到進(jìn)程結(jié)束才釋放。因此,在使用線程池的時(shí)候,線程、其他內(nèi)核對(duì)象、內(nèi)部數(shù)據(jù)結(jié)構(gòu)被分配給進(jìn)程,因此要考慮線程池是否確實(shí)必要。
線程池機(jī)制有4種功能:
1、調(diào)用一個(gè)異步函數(shù)
2、定時(shí)地調(diào)用一個(gè)函數(shù)
3、當(dāng)一個(gè)內(nèi)核對(duì)象被通知的時(shí)候調(diào)用一個(gè)函數(shù)
4、當(dāng)一個(gè)異步I/O請(qǐng)求完成的時(shí)候調(diào)用一個(gè)函數(shù)
而這4個(gè)功能都和線程池中的“工作項(xiàng)”息息相關(guān)。可以把“工作項(xiàng)”看作是一個(gè)特定的工作記錄,記錄著異步函數(shù),線程池定時(shí)器信息,線程池等待對(duì)象信息,線程池I/O對(duì)象,而這4個(gè)對(duì)象就是實(shí)現(xiàn)上述4個(gè)功能的要素。
調(diào)用一個(gè)異步函數(shù)
首先來(lái)討論一下第1種功能:調(diào)用一個(gè)異步函數(shù)。其基本步驟可以有兩種:
第一種:
1、定義一個(gè)給定格式的異步函數(shù)
2、提交這個(gè)異步函數(shù)給線程池
第二種:
1、定義一個(gè)給定格式的異步函數(shù)
2、創(chuàng)建一個(gè)“工作項(xiàng)”,該工作項(xiàng)與異步函數(shù)、異步函數(shù)參數(shù)關(guān)聯(lián)
3、將這個(gè)工作項(xiàng)提交給線程池
4、關(guān)閉創(chuàng)建的“工作項(xiàng)”
為了在線程池中調(diào)用一個(gè)異步函數(shù),該異步函數(shù)的定義如下(第1個(gè)參數(shù)pInstance暫不討論,可以簡(jiǎn)單地傳遞NULL,下同):
VOID NTAPI SimpleCallback( //函數(shù)名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext); //函數(shù)參數(shù)
然后,你可以提交一個(gè)請(qǐng)求給線程池,讓其中的一個(gè)線程執(zhí)行這個(gè)函數(shù):
BOOL TrySubmitThreadpoolCallback(
PTP_SIMPLE_CALLBACK pfnCallback, //按上面格式定義的異步函數(shù)的指針
PVOID pvContext, //傳遞給異步函數(shù)的參數(shù)
PTP_CALLBACK_ENVIRON pcbe);
TrySubmitThreadpoolCallback 函數(shù)在線程池隊(duì)列中加入一個(gè)“工作項(xiàng)”(work item),如果成功返回TRUE,否則返回FLASE。pcbe參數(shù)下面會(huì)介紹,你可以簡(jiǎn)單地傳遞NULL給這個(gè)參數(shù)(下同)。
你不必調(diào)用CreateThread來(lái)創(chuàng)建線程,當(dāng)進(jìn)程內(nèi)調(diào)用TrySubmitThreadpoolCallback函數(shù)的時(shí)候,系統(tǒng)會(huì)自動(dòng)地給你的進(jìn)程創(chuàng)建線程池,然后在該線程池隊(duì)列中隊(duì)列中加入一個(gè)“工作項(xiàng)”,并讓其中的一個(gè)線程來(lái)執(zhí)行你定義的異步函數(shù)。當(dāng)異步函數(shù)執(zhí)行完畢后,該線程不會(huì)被銷(xiāo)毀,而是進(jìn)入線程池等待另一個(gè)“工作項(xiàng)”的到來(lái)。線程池中的線程是回收利用的,并不是不斷創(chuàng)建和銷(xiāo)毀的,這樣提高了性能。同時(shí),這個(gè)線程池如果覺(jué)得自己的線程太多的話,就自動(dòng)地銷(xiāo)毀一些線程,可以讓性能達(dá)到最佳。
你可以使用CreateThreadpoolWork函數(shù)來(lái)創(chuàng)建一個(gè)“工作項(xiàng)”:
PTP_WORK CreateThreadpoolWork(
PTP_WORK_CALLBACK pfnWorkHandler, //異步函數(shù)指針
PVOID pvContext, //異步函數(shù)參數(shù)
PTP_CALLBACK_ENVIRON pcbe);
該函數(shù)接受一個(gè)異步函數(shù)的指針和這個(gè)異步函數(shù)的參數(shù),并創(chuàng)建一個(gè)用戶模式的數(shù)據(jù)結(jié)構(gòu)來(lái)保存對(duì)應(yīng)的3個(gè)參數(shù)的數(shù)據(jù),同時(shí)返回一個(gè)指向這個(gè)數(shù)據(jù)結(jié)構(gòu)的指針,可以理解為“工作項(xiàng)”指針。
其中,pfnWordHandler 函數(shù)是一個(gè)異步函數(shù)的指針,這個(gè)異步函數(shù)會(huì)被線程池中某個(gè)線程調(diào)用,該異步函數(shù)定義如下:
VOID CALLBACK WorkCallback( //函數(shù)名可以任意
PTP_CALLBACK_INSTANCE Instance,
PVOID Context, //異步函數(shù)參數(shù),由CreateThreadpoolWork函數(shù)指定
PTP_WORK Work); //線程池“工作項(xiàng)”指針
當(dāng)你想將一個(gè)創(chuàng)建了的工作項(xiàng)提交給線程池,可以使用SubmitThreadpoolWork函數(shù):
VOID SubmitThreadpoolWork(PTP_WORK pWork); //參數(shù)是工作項(xiàng)指針
如果多次調(diào)用該函數(shù)向一個(gè)線程池提交同一個(gè)工作項(xiàng),那么異步函數(shù)會(huì)被調(diào)用多次,而每次的參數(shù)都是同樣一個(gè)值。
如果有另一個(gè)線程想要取消提交的工作項(xiàng),或者掛起自己等待工作項(xiàng)完成,可以使用這個(gè)函數(shù):
VOID WaitForThreadpoolWorkCallbacks(
PTP_WORK pWork, //工作項(xiàng)指針
BOOL bCancelPendingCallbacks); //是否取消該工作項(xiàng)
pWork 函數(shù)是一個(gè)工作項(xiàng)指針,由函數(shù)CreateThreadpoolWork創(chuàng)建并返回。如果該工作項(xiàng)沒(méi)有被提交,則該函數(shù)馬上返回,不做任何工作。
如果傳遞TRUE給參數(shù)bCancelPendingCallbacks,WaitForThreadpoolWorkCallbacks函數(shù)將試圖取消這個(gè)先前提交的工作項(xiàng)。如果這個(gè)工作項(xiàng)正在被處理,那么這個(gè)處理不會(huì)被打斷,該函數(shù)會(huì)等待直到工作項(xiàng)結(jié)束才返回。如果這個(gè)工作項(xiàng)被提交,但是目前不在處理,那么該函數(shù)就會(huì)立即取消該工作項(xiàng)并理解返回,那么這個(gè)工作項(xiàng)的異步函數(shù)就不會(huì)被調(diào)用了。
如果傳遞FALSE給傳遞bCancelPendingCallbacks,WaitForThreadpoolWorkCallbacks函數(shù)將掛起這個(gè)調(diào)用它的線程,直到指定的工作項(xiàng)完成,而線程池中執(zhí)行這個(gè)工作項(xiàng)的線程在完成處理工作項(xiàng)之后返回線程池,繼續(xù)處理下一個(gè)工作項(xiàng)。
如果傳遞給WaitForThreadpoolWorkCallbacks函數(shù)的第一個(gè)參數(shù)的工作項(xiàng)指針被提交給線程池多次,也就是說(shuō)多個(gè)工作項(xiàng)使用同一個(gè)工作項(xiàng)指針,如果第2個(gè)參數(shù)為FALSE,那么WaitForThreadpoolWorkCallbacks將等到這個(gè)工作項(xiàng)指針代表的所有工作項(xiàng)處理完成才返回。如果傳遞TRUE給第2個(gè)參數(shù),WaitForThreadpoolWorkCallbacks將等待,只要當(dāng)前正在執(zhí)行的工作項(xiàng)結(jié)束就返回。
當(dāng)你不要使用工作項(xiàng)的時(shí)候,使用CloseThreadpoolWork函數(shù)關(guān)閉之。
VOID CloseThreadpoolWork(PTP_WORK pwk);
定時(shí)調(diào)用一個(gè)函數(shù)
這是Windows線程池提供的第2個(gè)功能。
有的時(shí)候,應(yīng)用程序需要在某一個(gè)特定的時(shí)間執(zhí)行特定的任務(wù),你可以選擇使用Windows內(nèi)核對(duì)象“等待定時(shí)器”來(lái)實(shí)現(xiàn)這個(gè)功能,但是如果這種基于時(shí)間的任務(wù)特別的多,那么就不得不為每個(gè)這樣的任務(wù)創(chuàng)建一個(gè)“等待定時(shí)器”對(duì)象,無(wú)疑會(huì)浪費(fèi)資源。當(dāng)然,你也許會(huì)想到創(chuàng)建單個(gè)“等待定時(shí)器”,然后不斷地設(shè)置它的下一次要等待的時(shí)間,這樣就可以完成多個(gè)基于時(shí)間的任務(wù)了。但是如此一來(lái),代碼量就會(huì)增大。
Windows提供了線程池來(lái)實(shí)現(xiàn)這樣的功能,其方法是通過(guò)“線程池定時(shí)器”。
首先,你要定義一個(gè)如下格式的回調(diào)函數(shù),讓線程池中的線程定時(shí)調(diào)用它:
VOID CALLBACK TimeoutCallback( // 函數(shù)名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext, // 函數(shù)的參數(shù)
PTP_TIMER pTimer); // 一個(gè)指向“線程池定時(shí)器”的指針
然后告訴線程池什么時(shí)候調(diào)用你的回調(diào)函數(shù):
PTP_TIMER CreateThreadpoolTimer(
PTP_TIMER_CALLBACK pfnTimerCallback, // 類(lèi)似上面格式的函數(shù)的指針
PVOID pvContext, // 回調(diào)函數(shù)的參數(shù),由這個(gè)參數(shù)指明
PTP_CALLBACK_ENVIRON pcbe);
不難發(fā)現(xiàn),CreateThreadpoolTimer函數(shù)和第1種方法中的CreateThreadpoolWork函數(shù)十分類(lèi)似,而且兩者的回調(diào)函數(shù)也十分類(lèi)似。當(dāng)調(diào)用CreateThreadpoolTimer函數(shù)的時(shí)候,第1個(gè)參數(shù)指向一個(gè)回調(diào)函數(shù),第2個(gè)參數(shù)pvContext會(huì)傳遞給這個(gè)回調(diào)函數(shù)的第2個(gè)參數(shù),而其返回值——一個(gè)“線程池定時(shí)器”指針也會(huì)傳遞給這個(gè)回調(diào)函數(shù)的第3個(gè)參數(shù)。
如果你想把由CreateThreadpoolTimer函數(shù)創(chuàng)建的“線程池定時(shí)器”注冊(cè)到線程池中去,可以使用如下函數(shù):
VOID SetThreadpoolTimer(
PTP_TIMER pTimer, // 一個(gè)“線程池定時(shí)器”指針
PFILETIME pftDueTime, // 回調(diào)函數(shù)被調(diào)用的時(shí)間
DWORD msPeriod, // 周期性調(diào)用回調(diào)函數(shù)的間隔時(shí)間(毫秒)
DWORD msWindowLength); // 周期時(shí)間的波動(dòng)范圍(毫秒)
該函數(shù)的第1個(gè)參數(shù)pTimer是由CreateThreadpoolTimer函數(shù)返回的。第2個(gè)參數(shù)pftDueTimer是指明回調(diào)函數(shù)什么時(shí)候被調(diào)用,一個(gè)正的數(shù)值表示的是絕對(duì)時(shí)間,即UTC統(tǒng)一時(shí)間;一個(gè)負(fù)數(shù)表示相對(duì)時(shí)間,即調(diào)用該函數(shù)之后開(kāi)始計(jì)時(shí),以毫秒為單位;如果是-1,表明回調(diào)函數(shù)馬上被調(diào)用。第3個(gè)參數(shù)msPeriod表明周期性地調(diào)用回調(diào)函數(shù)的時(shí)間間隔,即周期時(shí)間,如果只想回調(diào)函數(shù)調(diào)用一次,傳遞0給這個(gè)參數(shù)。第4個(gè)參數(shù)是和第3個(gè)參數(shù)聯(lián)用的,表明周期時(shí)間的波動(dòng)范圍,比如,msPeriod=1000,msWindowLength=2,那么回調(diào)函數(shù)會(huì)在每隔998、999、1000、1001、1002這5個(gè)可能的毫秒時(shí)間被調(diào)用。
如果一個(gè)“線程池定時(shí)器”已經(jīng)被SetThreadpoolTimer設(shè)置了,那么可以再次呼叫SetThreadpoolTimer函數(shù)來(lái)更改它的相關(guān)屬性。呼叫SetThreadpoolTimer的時(shí)候,可以把NULL傳遞給第2個(gè)參數(shù)pftDueTime,這樣就說(shuō)明讓線程池停止呼叫對(duì)應(yīng)的回調(diào)函數(shù)。
你可以查詢(xún)一個(gè)“線程池定時(shí)器”是否被設(shè)置,呼叫IsThreadpoolTimerSet函數(shù):
BOOL IsThreadpoolTimerSet(PTP_TIMER pti);
你也可以讓線程等待一個(gè)“線程池定時(shí)器”完成工作,呼叫函數(shù)WaitForThreadpoolTimerCallbacks,當(dāng)要關(guān)閉一個(gè)“線程池定時(shí)器”的時(shí)候,呼叫函數(shù)CloseThreadpoolTimer,這兩個(gè)函數(shù)同前面討論的WaitForThreadpoolWork和CloseThreadpoolWorkCallbacks函數(shù)類(lèi)似,可以參考本篇前面的內(nèi)容。
下面總結(jié)一下“線程池定時(shí)器”的使用方法:
- 定義一個(gè)回調(diào)函數(shù),如TimeoutCallback那樣的格式。
- 使用CreateThreadpoolTimer函數(shù)創(chuàng)建一個(gè)“線程池定時(shí)器”,并將已定義的回調(diào)函數(shù)與它關(guān)聯(lián)在了一起。
- 使用SetThreadpoolTimer設(shè)置“線程池定時(shí)器”的屬性,并將其提交給線程池。
- 調(diào)用CloseThreadpoolTimer關(guān)閉“線程池定時(shí)器”。
當(dāng)一個(gè)內(nèi)核對(duì)象被通知的時(shí)候調(diào)用一個(gè)函數(shù)
有很多線程,初始化的時(shí)候等待一個(gè)內(nèi)核對(duì)象,一旦這個(gè)內(nèi)核對(duì)象轉(zhuǎn)入“已通知”狀態(tài),線程就會(huì)通知另外一些線程,然后轉(zhuǎn)回繼續(xù)等待這個(gè)內(nèi)核對(duì)象。但是,如果這樣的線程很多的話,無(wú)疑會(huì)增大系統(tǒng)的開(kāi)銷(xiāo)。
此時(shí),你可以考慮使用線程池來(lái)實(shí)現(xiàn)這個(gè)功能,就是當(dāng)一個(gè)內(nèi)核對(duì)象被通知的時(shí)候,由線程池中的一個(gè)線程調(diào)用一個(gè)異步的回調(diào)函數(shù)。
如果你想讓一個(gè)“工作項(xiàng)”在一個(gè)內(nèi)核對(duì)象為“已通知”的狀態(tài)下被執(zhí)行,這個(gè)基本流程和前面兩個(gè)功能的流程類(lèi)似。
首先,定義一個(gè)如下格式的異步函數(shù):
VOID CALLBACK WaitCallback( // 函數(shù)名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID Context, // 函數(shù)的參數(shù)
PTP_WAIT Wait, // 線程池等待對(duì)象的指針
TP_WAIT_RESULT WaitResult); // 該函數(shù)被調(diào)用的原因
然后,需要?jiǎng)?chuàng)建一個(gè)“線程池等待對(duì)象”:
PTP_WAIT CreateThreadpoolWait(
PTP_WAIT_CALLBACK pfnWaitCallback, // 回調(diào)函數(shù)指針,函數(shù)如上定義
PVOID pvContext, // 傳遞給回調(diào)函數(shù)參數(shù)Context
PTP_CALLBACK_ENVIRON pcbe);
接著就可以將創(chuàng)建的“線程池等待對(duì)象”與這個(gè)線程池關(guān)聯(lián)起來(lái),此時(shí)線程池隊(duì)列中會(huì)有一個(gè)“等待項(xiàng)”記錄:
VOID SetThreadpoolWait(
PTP_WAIT pWaitItem, // 一個(gè)“線程池等待對(duì)象”指針
HANDLE hObject, // 一個(gè)內(nèi)核對(duì)象句柄,當(dāng)被通知時(shí),回調(diào)函數(shù)被調(diào)用
PFILETIME pftTimeout); // 等待hObjetct內(nèi)核對(duì)象受到通知的時(shí)間
這個(gè)函數(shù)的第1個(gè)參數(shù)pWaitItem很顯然是從CreateThreadpoolWait成功返回的“線程池等待對(duì)象”指針。第2個(gè)參數(shù)hObject是一個(gè)內(nèi)核對(duì)象句柄,當(dāng)這個(gè)內(nèi)核對(duì)象為“已通知”狀態(tài),則線程池中的一個(gè)線程調(diào)用異步回調(diào)函數(shù)。第3個(gè)參數(shù)pftTimeout是一個(gè)等待內(nèi)核對(duì)象的時(shí)間,如果為0表示不等待;傳遞一個(gè)負(fù)數(shù)表示一個(gè)相對(duì)時(shí)間;傳遞一個(gè)正數(shù)表示絕對(duì)時(shí)間;傳遞NULL表示無(wú)限期地等待。
要注意的是,不要多次使用SetThreadpoolWait來(lái)等待同一個(gè)hObject。
當(dāng)內(nèi)核對(duì)象被通知或者等待時(shí)間超出,線程池中的線程將呼叫你的回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)的最后一個(gè)參數(shù)WaitResult的值,其實(shí)是一個(gè)DOWRD類(lèi)型的,它指明的該回調(diào)函數(shù)被調(diào)用的原因:
1、WAIT_OBJECT_0:SetThreadpoolWait中第二個(gè)參數(shù)hObject所表明的內(nèi)核對(duì)象受到通知。
2、WAIT_TIMEOUT:內(nèi)核對(duì)象受到通知的時(shí)間超過(guò)了SetThreadpoolWait的第三個(gè)參數(shù)所設(shè)置的等待時(shí)間。
3、WAIT_ABANDONED_0:SetThreadWait函數(shù)第二個(gè)參數(shù)hObject代表一個(gè)互斥內(nèi)核對(duì)象,而這個(gè)互斥內(nèi)核對(duì)象被丟棄。
一旦一個(gè)線程池線程調(diào)用了你的回調(diào)函數(shù),那么對(duì)應(yīng)的“等待項(xiàng)”就不活躍了,你必須使用相同的參數(shù)再次調(diào)用SetThreadpoolWait函數(shù)來(lái)提交一個(gè)等待項(xiàng)。
如果想刪除一個(gè)“等待項(xiàng)”,可以使用與之對(duì)應(yīng)的“線程池等待對(duì)象”指針來(lái)調(diào)用SetThreadpoolWait,并將hObejct參數(shù)設(shè)置為NULL。
最后,你也可以使用WaitForThreadpoolWaitCallbacks來(lái)等待對(duì)應(yīng)的“等待項(xiàng)”結(jié)束,也可以使用CloseThreadpoolWait來(lái)關(guān)閉一個(gè)“等待項(xiàng)”。這兩個(gè)參數(shù)和WaitForThreadpoolWorkCallbakcs和CloseThreadpoolWork是類(lèi)似的。
當(dāng)異步I/O請(qǐng)求結(jié)束的時(shí)候調(diào)用一個(gè)函數(shù)
讀過(guò)上面3中線程池的功能,不難發(fā)現(xiàn)有很多共同的特點(diǎn),連函數(shù)名稱(chēng)都很有規(guī)律。線程池中的線程由系統(tǒng)統(tǒng)一管理,自動(dòng)地創(chuàng)建和銷(xiāo)毀。其實(shí),這些線程內(nèi)部都在等待一個(gè)I/O完成端口,這個(gè)I/O完成端口稱(chēng)為“線程池的I/O完成端口”。
如果你要使用線程池來(lái)處理設(shè)備異步I/O請(qǐng)求的時(shí)候,當(dāng)你打開(kāi)一個(gè)設(shè)備的時(shí)候,必須首先將這個(gè)設(shè)備與“線程池I/O完成端口”關(guān)聯(lián)起來(lái),然后告訴線程池當(dāng)設(shè)備異步I/O請(qǐng)求結(jié)束之后哪個(gè)函數(shù)將被調(diào)用。
首先,定義一個(gè)如下格式的異步回調(diào)函數(shù):
VOID CALLBACK OverlappedCompletionRoutine( // 函數(shù)名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext, // 該函數(shù)的一個(gè)參數(shù)
PVOID pOverlapped, // OVERLAPPED結(jié)構(gòu)指針
ULONG IoResult, // I/O請(qǐng)求結(jié)果,如果成功,則為NO_ERROR
ULONG_PTR NumberOfBytesTransferred, // I/O請(qǐng)求的數(shù)據(jù)傳輸字節(jié)數(shù)
PTP_IO pIo); // 一個(gè)“線程池I/O完成項(xiàng)”指針
這個(gè)函數(shù)的最后一個(gè)參數(shù)pIo是一個(gè)PTP_IO類(lèi)型,即一個(gè)線程池I/O完成項(xiàng),它與“線程池工作項(xiàng)”和“線程池等待項(xiàng)”是類(lèi)似的。你必須創(chuàng)建它,使用如下函數(shù):
PTP_IO CreateThreadpoolIo(
HANDLE hDevice, // 與線程池I/O完成端口關(guān)聯(lián)的設(shè)備對(duì)象句柄
PTP_WIN32_IO_CALLBACK pfnIoCallback, // 如上格式的異步回調(diào)函數(shù)指針
PVOID pvContext, // 該參數(shù)在調(diào)用時(shí)傳遞給回調(diào)函數(shù)的第2個(gè)參數(shù)
PTP_CALLBACK_ENVIRON pcbe);
該函數(shù)將hDevice參數(shù)所對(duì)應(yīng)的設(shè)備記錄到線程池I/O項(xiàng)中,然后,可以使用如下函數(shù)將設(shè)備與線程池I/O完成端口關(guān)聯(lián)起來(lái):
VOID StartThreadpoolIo(PTP_IO pio);
注意,StartThreadpoolIo函數(shù)必須在ReadFile和WriteFile之前調(diào)用,如果沒(méi)有在它們之前調(diào)用,你的異步回調(diào)函數(shù)不會(huì)被調(diào)用。
當(dāng)你想停止調(diào)用回調(diào)函數(shù)的時(shí)候,可以使用CancelThreadpoolIo,如果在調(diào)用ReadFile或WriteFile之后,它們的返回值是FLASE,而GetLastError的返回值不是ERROR_IO_PENDING,那么也應(yīng)該調(diào)用CancelThreadpoolIo:
VOID CancelThreadpoolIo(PTP_IO pio);
當(dāng)結(jié)束了設(shè)備I/O,你應(yīng)該使用CloseHandle關(guān)閉設(shè)備句柄,然后呼叫CloseThradpoolIo關(guān)閉線程池I/O項(xiàng),即取消設(shè)備與線程池I/O請(qǐng)求的關(guān)聯(lián)。
VOID CloseThreadpoolIo(PTP_IO pio);
另外,你可以讓一個(gè)線程等待I/O請(qǐng)求結(jié)束:
VOID WaitForThreadpoolIoCallbacks(
PTP_IO pio,
BOOL bCancelPendingCallbacks); // 是否取消回調(diào)函數(shù)的調(diào)用
如果給這個(gè)函數(shù)的參數(shù)BCancelPendingCallbacks傳遞TRUE,那么回調(diào)函數(shù)將不會(huì)被調(diào)用,該函數(shù)的用法和WaitForThreadpoolWork是類(lèi)似的。
回調(diào)函數(shù)結(jié)束之后的行為
注意上面討論的各種類(lèi)型的回調(diào)函數(shù)第1個(gè)參數(shù),是一個(gè)PTF_CALLBACK_INSTANCE類(lèi)型的數(shù)據(jù)pInstance,從字面上看,是“線程池回調(diào)函數(shù)實(shí)體指針”,也就是說(shuō),這個(gè)數(shù)據(jù)是各個(gè)回調(diào)函數(shù)唯一的,是回調(diào)函數(shù)的標(biāo)識(shí),這個(gè)數(shù)據(jù)是在調(diào)用回調(diào)函數(shù)之前由系統(tǒng)自動(dòng)分配的,可以用這個(gè)參數(shù)調(diào)用如下函數(shù):
// 當(dāng)回調(diào)函數(shù)返回的時(shí)候,自動(dòng)離開(kāi)一個(gè)指定的關(guān)鍵代碼段
VOID LeaveCriticalSectionWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci, // 回調(diào)函數(shù)實(shí)體指針,標(biāo)識(shí)一個(gè)回調(diào)函數(shù)
PCRITICAL_SECTION pcs); // 關(guān)鍵代碼段結(jié)構(gòu)指針,標(biāo)識(shí)一個(gè)關(guān)鍵代碼段
// 當(dāng)回調(diào)函數(shù)返回的時(shí)候,自動(dòng)釋放一個(gè)指定的互斥內(nèi)核對(duì)象
VOID ReleaseMutexWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HANDLE mut);
// 當(dāng)回調(diào)函數(shù)返回的時(shí)候,自動(dòng)釋放一個(gè)指定的信號(hào)量?jī)?nèi)核對(duì)象
VOID ReleaseSemaphoreWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HANDLE sem,
DWORD crel);
// 當(dāng)回調(diào)函數(shù)返回的時(shí)候,自動(dòng)將一個(gè)事件內(nèi)核對(duì)象設(shè)置為已通知狀態(tài)
VOID SetEventWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HANDLE evt);
// 當(dāng)回調(diào)函數(shù)返回的時(shí)候,自動(dòng)卸載一個(gè)模塊
VOID FreeLibraryWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HMODULE mod);
這些函數(shù)的第1個(gè)參數(shù)pci標(biāo)識(shí)當(dāng)線程池前正在處理的工作、定時(shí)器、等待、I/O項(xiàng),調(diào)用這些函數(shù),表示對(duì)應(yīng)的回調(diào)函數(shù)結(jié)束之后,所做的一些釋放和設(shè)置工作。
其中,前4個(gè)函數(shù),提供了一種方法來(lái)通知其他線程,說(shuō)明線程池中的某一個(gè)工作項(xiàng)完成。最后一個(gè)函數(shù),提供了一種方法來(lái)卸載DLL的方法,特別是當(dāng)回調(diào)函數(shù)是從DLL中導(dǎo)出的時(shí)候,這種方法特別適用。要注意的是,只能有一個(gè)動(dòng)作在回調(diào)函數(shù)返回的時(shí)候被執(zhí)行,你不能多次呼叫上述5個(gè)函數(shù),這樣的話,最后一次呼叫的函數(shù)會(huì)覆蓋前面所呼叫的函數(shù),因此,不能同時(shí)離開(kāi)關(guān)鍵代碼段并釋放信號(hào)量?jī)?nèi)核對(duì)象。
另外,還有兩個(gè)函數(shù)需要回調(diào)函數(shù)實(shí)體指針:
BOOL CallbackMayRunLong(PTP_CALLBACK_INSTANCE pci);
VOID DisassociateCurrentThreadFromCallback(PTP_CALLBACK_INSTANCE pci);
CallbackMayRunLong并不是設(shè)置回調(diào)函數(shù)結(jié)束時(shí)的工作的,而是當(dāng)一個(gè)回調(diào)函數(shù)認(rèn)為自己執(zhí)行的時(shí)間可能比較長(zhǎng)才可能需要呼叫這個(gè)函數(shù)。此時(shí),線程池不會(huì)創(chuàng)建新的線程,以此來(lái)提高這個(gè)回調(diào)函數(shù)的性能。當(dāng)該函數(shù)返回FLASE,線程池不允許其他線程能夠處理線程池隊(duì)列中的其他項(xiàng);如果返回TRUE,表示線程池允許其他線程處理其他工作項(xiàng)。
DisassociateCurrentThreadFromCallback函數(shù)表明與回調(diào)函數(shù)關(guān)聯(lián)的工作項(xiàng)在“邏輯上”完成了(其實(shí)不是真正完成),此時(shí)允許等待在這個(gè)工作項(xiàng)上的函數(shù)返回,比如WaitForThreadpoolWorkCallbacks、WaitForThreadpoolTimerCallbacks、WaitForThreadpoolWaitCallbacks、WaitForThreadpoolIoCallbacks這些函數(shù)返回。
定制線程池
以上所討論的線程池,都是系統(tǒng)自動(dòng)控制的,用戶無(wú)法改變其內(nèi)部的流程。
下面,我們討論一下如何自己定制線程池。
你會(huì)注意到,上面的每個(gè)“創(chuàng)建”函數(shù):CreateThreadpoolWork、CreateThreadpoolTimer、CreateThreadpoolWait、CreateThreadpoolIo以及TrySubmitThreadpoolCallback這5個(gè)函數(shù)中的最后一個(gè)參數(shù)pcbe,一個(gè)類(lèi)型為PTP_CALLBACK_ENVIRON的參數(shù),一個(gè)指向“回調(diào)函數(shù)環(huán)境”結(jié)構(gòu)的指針。你可以簡(jiǎn)單地傳遞NULL給這個(gè)參數(shù),表明你使用默認(rèn)的系統(tǒng)自動(dòng)分配和管理的進(jìn)程線程池。
但是,有的時(shí)候程序員喜歡自己來(lái)控制線城池,給線城池設(shè)置一些規(guī)則和屬性。比如設(shè)置改線城池中線程的數(shù)量上下限,或者想操縱線城池中線程的創(chuàng)建和銷(xiāo)毀。
要達(dá)到這個(gè)目的,可以自己創(chuàng)建線程池,然后設(shè)置一些屬性。
首先,創(chuàng)建一個(gè)線程池,使CreateThreadpool函數(shù):
PTP_POOL CreateThreadpool(PVOID reserved); // 參數(shù)是保留參數(shù),必須為NULL
該函數(shù)返回一個(gè)PTP_POOL類(lèi)型的數(shù)據(jù),姑且認(rèn)為是“線城池指針”的意思,即代表了一個(gè)線程池。
然后,就可以通過(guò)這個(gè)線城池指針來(lái)呼叫相應(yīng)的API函數(shù),設(shè)置線程池的一些屬性了。
你可以設(shè)置線城池的線程數(shù)量的上下限:
BOOL SetThreadpoolThreadMinimum(
PTP_POOL pThreadPool,
DWORD cthrdMin);
BOOL SetThreadpoolThreadMaximum(
PTP_POOL pThreadPool,
DWORD cthrdMost);
通過(guò)呼叫這兩個(gè)函數(shù)之后,線程池中的線程的數(shù)量決不會(huì)少于設(shè)置的最小值,并且允許這個(gè)數(shù)量增大到最大值。順便說(shuō)一下,默認(rèn)的線城池的線程數(shù)量范圍為1~500。
當(dāng)一個(gè)線程池需要關(guān)閉的時(shí)候,呼叫CloseThreadpool函數(shù):
VOID CloseThreadpool(PTP_POOL pThreadPool);
呼叫這個(gè)函數(shù)之后,對(duì)應(yīng)的線程池隊(duì)列中的項(xiàng)都不會(huì)被處理,當(dāng)前正在處理工作項(xiàng)的線程都會(huì)結(jié)束處理然后線程終止,其他還沒(méi)有被處理的項(xiàng)都會(huì)被取消。
一旦你創(chuàng)建了你自己的線程池并設(shè)置了線程數(shù)量上下限,你就初始化那個(gè)“回調(diào)函數(shù)環(huán)境”結(jié)構(gòu)了,該結(jié)構(gòu)中包含了另外的一些設(shè)置。這個(gè)結(jié)構(gòu)與一個(gè)工作項(xiàng)有關(guān)。該結(jié)構(gòu)定義如下:
typedef struct _TP_CALLBACK_ENVIRON {
TP_VERSION Version;
PTP_POOL Pool; // 所屬哪個(gè)線程池
PTP_CLEANUP_GROUP CleanupGroup;
PTP_CLEANUP_GROUP_CANCEL_CALLBACK CleanupGroupCancelCallback;
PVOID RaceDll;
struct _ACTIVATION_CONTEXT *ActivationContext;
PTP_SIMPLE_CALLBACK FinalizationCallback;
union {
DWORD Flags;
struct {
DWORD LongFunction : 1;
DWORD Private : 31;
} s;
} u;
} TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;
你最好不要自己去初始化該結(jié)構(gòu),而是應(yīng)該使用如下函數(shù):
VOID InitializeThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe);
該函數(shù)將結(jié)構(gòu)中的各個(gè)字段設(shè)置為0,除了Version被設(shè)置為1。
當(dāng)你不再需要使用該結(jié)構(gòu)的時(shí)候,使用如下函數(shù)刪除它:
VOID DestroyThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe);
在初始化TP_CALLBACK_ENVIRON結(jié)構(gòu)之后,該結(jié)構(gòu)與一個(gè)工作項(xiàng)相關(guān),然后你就可以使用這個(gè)結(jié)構(gòu)來(lái)提交一個(gè)工作項(xiàng)給指定的線程池了:
VOID SetThreadpoolCallbackPool(
PTP_CALLBACK_ENVIRON pcbe, // 回調(diào)函數(shù)環(huán)境結(jié)構(gòu)指針
PTP_POOL pThreadPool); // 線程池指針,由CreateThreadpool函數(shù)返回
如果不調(diào)用該函數(shù),那么回調(diào)函數(shù)環(huán)境結(jié)構(gòu)中的Pool成員的值就是NULL,表示不為任何定制的線程池相關(guān),那么與此結(jié)構(gòu)相關(guān)的工作項(xiàng)就會(huì)提交給默認(rèn)線程池。
如果一個(gè)工作項(xiàng)處理的時(shí)間比較長(zhǎng),可以調(diào)用SetThreadpoolCallbackRunsLong函數(shù),這會(huì)導(dǎo)致線程池更快地創(chuàng)建線程來(lái)處理這個(gè)工作項(xiàng)。
VOID SetThreadpoolCallbackRunsLong(PTP_CALLBACK_ENVIRON pcbe);
你可以呼叫SetThreadpoolCallbackLibrary來(lái)確保當(dāng)某個(gè)工作項(xiàng)未完成的時(shí)候,一個(gè)DLL始終被加載到進(jìn)程地址中。該函數(shù)也可以除去潛在的死鎖。
VOID SetThreadpoolCallbackLibrary(
PTP_CALLBACK_ENVIRON pcbe,
PVOID mod); // 模塊指針,就是模塊句柄
線程池要處理很多的項(xiàng)目,因此很難確定這些項(xiàng)目什么時(shí)候處理結(jié)束,這也使得線程池的清除工作困難了。為了解決這種情況,線程池提供了一種機(jī)制——“清理組”。要注意的是,該機(jī)制不適合于默認(rèn)線程池,只適合于定制的線程池。因?yàn)槟J(rèn)線程池的生命期與進(jìn)程一樣,系統(tǒng)會(huì)在進(jìn)程結(jié)束之后清除默認(rèn)線程池。
為了使用“清理組”機(jī)制,首先,你需要?jiǎng)?chuàng)建一個(gè)清理組:
PTP_CLEANUP_GROUP CreateThreadpoolCleanupGroup();
然后將已創(chuàng)建的清理組與線程池關(guān)聯(lián)起來(lái):
VOID SetThreadpoolCallbackCleanupGroup(
PTP_CALLBACK_ENVIRON pcbe, // 回調(diào)函數(shù)環(huán)境結(jié)構(gòu)指針
PTP_CLEANUP_GROUP ptpcg, // 清理組指針
PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng); // 回調(diào)函數(shù)指針
該函數(shù)在內(nèi)部將回調(diào)函數(shù)指針p的cbe參數(shù)CleanupGroup和CleanupCancelCallback成員設(shè)置為第2和第3個(gè)參數(shù)所提供的值。如果清理組被取消,第3個(gè)參數(shù)pfng表示的回調(diào)函數(shù)將會(huì)被調(diào)用,這個(gè)回調(diào)函數(shù)必須滿足如下格式:
VOID CALLBACK CleanupGroupCancelCallback( // 函數(shù)名可以任意
PVOID pvObjectContext,
PVOID pvCleanupContext);
每次你調(diào)用CreateThreadpoolWork、CreateThreadpoolTimer、CreateThreadpoolWait和CreateThreadpoolIo的時(shí)候,如果傳遞給它們的最后一個(gè)參數(shù)pcbe不是NULL,那么一項(xiàng)就會(huì)加入相關(guān)的組清理中,當(dāng)這樣的項(xiàng)都完成之后,你調(diào)用CloseThreadpoolWork、CloseThreadpoolTimer、CloseThreadpoolWait、ClsoeThreadpoolIo的時(shí)候,又暗中地將這樣的項(xiàng)從組清理中刪除了。
這個(gè)時(shí)候,想要?jiǎng)h除線程池,可以調(diào)用如下函數(shù):
VOID CloseThreadpoolCleanupGroupMembers(
PTP_CLEANUP_GROUP ptpcg, // 組清理指針
BOOL bCancelPendingCallbacks, // 是否取消即將開(kāi)始執(zhí)行的回調(diào)函數(shù)
PVOID pvCleanupContext); // 傳入CleanupGroupCancelCallback的參數(shù)
這個(gè)函數(shù)同以前的WaitForThreadpool*函數(shù)類(lèi)似,當(dāng)一個(gè)線程呼叫它時(shí),它等待,直到所有的在線程工作組的項(xiàng)完成處理。如果第2個(gè)參數(shù)為T(mén)RUE,會(huì)取消所有還沒(méi)有開(kāi)始執(zhí)行的項(xiàng)目,然后等待當(dāng)前正在執(zhí)行的項(xiàng)目,直到它們執(zhí)行完成后該函數(shù)返回。如果第2個(gè)參數(shù)為T(mén)RUE,而且SetThreadpoolCallbackCleanupGroup的最后一個(gè)參數(shù)pfng傳遞了一個(gè)回調(diào)函數(shù)指針,那么這個(gè)回調(diào)函數(shù)就會(huì)為每個(gè)被取消的項(xiàng)目調(diào)用一次,CloseThreadpoolCleanupGroupMembers函數(shù)的第3個(gè)參數(shù)pvCleanupContext就會(huì)傳入回調(diào)函數(shù)的第2個(gè)參數(shù)pvCleanupContext。
如果在呼叫CloseThreadpoolCleanupGroupMembers函數(shù)的時(shí)候,傳遞FLASE給第2個(gè)參數(shù),那么該函數(shù)就會(huì)等待線程池中隊(duì)列中的所有的項(xiàng)完成之后才返回,此時(shí)回調(diào)函數(shù)不會(huì)被調(diào)用,因此可以傳遞NULL給pvCleanupContext參數(shù)。
當(dāng)ClsetThreadpoolCleanupGroupMembers函數(shù)返回之后,你需要呼叫CloseThreadpoolCleanupGroup來(lái)關(guān)閉一個(gè)線程池清理組:
VOID WINAPI CloseThreadpoolCleanupGroup(PTP_CLEANUP_GROUP ptpcg);
最后,要調(diào)用DestroyThreadpoolEvironment和CloseThreadpool函數(shù)來(lái)清除回調(diào)函數(shù)環(huán)境結(jié)構(gòu)和關(guān)閉線程池。
小結(jié)
通過(guò)前面的敘述,線程池的操作流程是比較固定的:
1、定義異步回調(diào)函數(shù)
2、創(chuàng)建相關(guān)項(xiàng)
3、提交或設(shè)置項(xiàng)
4、線程池執(zhí)行
5、關(guān)閉相關(guān)項(xiàng)
自己寫(xiě)了一段代碼,總結(jié)了前面的知識(shí),代碼中省略了第1步,即沒(méi)有定義回調(diào)函數(shù),因?yàn)檫@是根據(jù)需要而編寫(xiě)的。如果代碼中有錯(cuò)誤,還請(qǐng)大家指出。
PTP_POOL pThreadpool = CreateThreadpool(NULL); // 創(chuàng)建線程池
// 設(shè)置線程池線程數(shù)量上下限
SetThreadpoolThreadMinimum(pThreadpool, 2);
SetThreadpoolThreadMaximum(pThreadpool, 10);
// 初始化“回調(diào)函數(shù)環(huán)境”結(jié)構(gòu)
TP_CALLBACK_ENVIRON tcbe;
InitializeThreadpoolEnvironment(&tcbe);
// 將該回調(diào)函數(shù)環(huán)境結(jié)構(gòu)與線程池相關(guān)聯(lián)
SetThreadpoolCallbackPool(&tcbe, pThreadpool);
// 創(chuàng)建清理組
PTP_CLEANUP_GROUP pTpcg= CreateThreadpoolCleanupGroup();
// 將回調(diào)函數(shù)環(huán)境結(jié)構(gòu)與清理組關(guān)聯(lián)起來(lái)
SetThreadpoolCallbackCleanupGroup(&tcbe, pTpcg, NULL);
// 現(xiàn)在可以創(chuàng)建一些項(xiàng),提交給線程池
PTP_WORK pTpWork = CreateThreadpoolWork(
, &tcbe);// 創(chuàng)建一個(gè)工作項(xiàng)
SubmitThreadpoolWork(pTpWork); // 提交工作項(xiàng)
PTP_TIMER pTpTimer = CreateThreadpoolTimer(
, &tcbe);// 創(chuàng)建一個(gè)定時(shí)器項(xiàng)
SetThreadpoolTimer(pTpTimer,
); // 提交定時(shí)器
PTP_WAIT pTpWait = CreateThreadpoolWait(
, &tcbe);// 創(chuàng)建一個(gè)等待項(xiàng)
SetThreadpoolWait(pTpWait,
); // 提交等待項(xiàng)
PTP_IO pTpIO = CreateThreadpoolIo(
, &tcbe); // 創(chuàng)建一個(gè)IO項(xiàng)
StartThreadpoolIo(pTpIO); // 開(kāi)始執(zhí)行IO項(xiàng)
// 等待所有項(xiàng)完成
CloseThreadpoolCleanupGroupMembers(pTpcg, FALSE, NULL);
// 關(guān)閉各個(gè)項(xiàng)
CloseThreadpoolWork(pTpWork);
CloseThreadpoolTimer(pTpTimer);
CloseThreadpoolWait(pTpWait);
CloseThreadpoolIo(pTpIO);
CloseThreadpoolCleanupGroup(pTpcg); // 關(guān)閉線程池清理組
DestroyThreadpoolEnvironment(&tcbe); // 刪除回調(diào)函數(shù)環(huán)境結(jié)構(gòu)
CloseThreadpool(pThreadpool); // 關(guān)閉線程池
posted on 2011-10-10 09:25
Yu_ 閱讀(845)
評(píng)論(0) 編輯 收藏 引用 所屬分類(lèi):
Windows程序設(shè)計(jì)