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

SubmitThreadpoolWork(pTpWork); // 提交工作項
PTP_TIMER pTpTimer = CreateThreadpoolTimer(

SetThreadpoolTimer(pTpTimer,

PTP_WAIT pTpWait = CreateThreadpoolWait(

SetThreadpoolWait(pTpWait,

PTP_IO pTpIO = CreateThreadpoolIo(

StartThreadpoolIo(pTpIO); // 開始執(zhí)行IO項
// 等待所有項完成
CloseThreadpoolCleanupGroupMembers(pTpcg, FALSE, NULL);
// 關(guān)閉各個項
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)閉線程池