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