Windows下一个比较完美的线程池实现
1.??前言
線程池不是一個新鮮的東西,網(wǎng)上能找到很多原理、實現(xiàn),甚至很多庫都提供了實現(xiàn),比如微軟的 ATL::CThreadPool, Vista后提供的CreateThreadpoolWork, boost 中提供的 thread_pool, CSDN、CodeProject 等網(wǎng)站上很多人已經(jīng)實現(xiàn)的類。但這些庫往往只支持啟動任務(wù),而不能很好地停止任務(wù)(相信很多人都會和我一樣有這個需求),于是我在FTL中寫了一個個人認為還比較完美的線程池。
2.??功能
本線程池提供了如下功能:
1.?????? 能根據(jù)任務(wù)個數(shù)和當前線程的多少在最小/最大線程個數(shù)之間自動調(diào)整(Vista后的系統(tǒng)有 SetThreadpoolThreadMaximum 等函數(shù)有類似功能);
2.??????能方便的對任一任務(wù)進行取消操作,無論該任務(wù)是等待運行狀態(tài)還是正在運行狀態(tài)都支持(相比較而言,WaitForThreadpoolWorkCallbacks 函數(shù)只能取消尚未運行的任務(wù));
3.??????能對整個線程池進行安全的暫停、繼續(xù)、停止處理
4.??????支持回調(diào)方式的反饋通知
5.??????使用模版方式實現(xiàn),能方便的進行參數(shù)傳遞
6.??????在加入任務(wù)時可以設(shè)置優(yōu)先級(目前尚不支持動態(tài)調(diào)整)
7.??????使用的是微軟的基本API,能支持WinXP、Vista、Win7等各種操作系統(tǒng)(CreateThreadpoolWork 等只能在Vista后才能使用)
3.??UML圖示和簡單說明
其UML圖比較簡單,主要的只有三個類:
CFThreadPool – 線程池的管理類,負責整個線程池的管理工作,直接使用即可。
CFJobBase – Job的基類,如果想實現(xiàn)自己的Job,必須從這個類繼承,并實現(xiàn)其中的Run/ OnCancelJob 等函數(shù)。
IFThreadPoolCallBack– 可選的回調(diào)實現(xiàn),可以通知調(diào)用段 Job 的啟動、停止、取消、進度通知、錯誤等各種狀態(tài)的改變。
4.??實現(xiàn)說明
以下部分簡單介紹了一些比較重要的代碼實現(xiàn),具體請參見示例代碼和其中的注釋部分。
4.1.?????Job容器
本線程池類中有兩種Job的容器,分別是等待運行的Jobs和當前正在運行的Jobs。因為有不同的需求,其定義分別如下(兩種容器類型的選擇,其理由已經(jīng)寫得比較詳細,大家自行分析即可)
???? //! 保存等待Job的信息,由于有優(yōu)先級的問題,而且從最前面開始取任務(wù),因此保存成set
//! 保證優(yōu)先級高、JobIndex小(同優(yōu)先級時FIFO) 的Job在最前面
???? typedeftypenameUnreferenceLess<CFJobBase<T>* >JobBaseUnreferenceLess;
???? typedefstd::set<CFJobBase<T>*,JobBaseUnreferenceLess > WaitingJobContainer;
???? WaitingJobContainer????????m_WaitingJobs;????//!等待運行的Job
?
???? //! 保存運行Job的信息,由于會頻繁加入、刪除,且需要按照JobIndex查找刪除,因此保存成map
???? typedefstd::map<LONG,CFJobBase<T>* >???DoingJobContainer;
???? DoingJobContainer????? m_DoingJobs;? //! 正在運行的Job
?
4.2.?????Job優(yōu)先級
代碼中通過operator < 方法比較CFJobBase<T>::m_nJobPriority和 JobIndex,在SubmitJob時保證其在 set 中的順序來保證 優(yōu)先級高的Job先執(zhí)行,相同優(yōu)先級的Job采用FIFO方式。
4.3.?????Job對暫停、停止的支持
為了支持暫停、停止,Job的子類必須在工作循環(huán)中調(diào)用父類提供 GetJobWaitType方法,并判斷其返回值。如果返回值為ftwtStop 表示用戶請求了停止,需要進行必要的清除工作。GetJobWaitType的實現(xiàn)請參見代碼(本質(zhì)是等待m_hEventJobStop, m_pThreadPool->m_hEventStop, m_pThreadPool->m_hEventContinue這三個手動重置事件之一)
4.4.?????線程池對Job的控制
線程池中主要有以下的一些函數(shù),因為意義根據(jù)名字很容易猜出,就不再詳細介紹,詳見示例。
Start(LONGnMinNumThreads, LONG nMaxNumThreads);
StopAndWait(DWORDdwTimeOut = FTL_MAX_THREAD_DEADLINE_CHECK);
ClearUndoWork();
SubmitJob(CFJobBase<T>*pJob, LONG* pOutJobIndex); //加入Job,會返回Job在Pool中的唯一索引,可以通過CancelJob 取消。
Pause/Resume/Stop// 對整個線程池請求暫停、繼續(xù)、停止的操作。注意:需要Job子類的配合才能達成目標。
4.5.?????資源的處理
CFJobBase子類需要 Initialize、Run、Finalize、OnCancelJob 這幾個虛函數(shù)。
當Job運行的時候,其邏輯為 if(Initialize){ Run -> Finalize; },需要在 Finalize 中釋放資源;
當Job沒有運行的時候就被取消或Pool停止,則會調(diào)用 OnCancelJob,需要在其中釋放資源。
5.??示例程序
編寫了簡單的MFC示例程序,其界面如圖所示:
因為UI不是重點,而且本人也比較懶,所以沒有弄很好的UI出來,所有的運行信息請參見VisualStudio中的“輸出”窗口。
Start 按鈕啟動線程池,示例中設(shè)置的最小、最大線程個數(shù)是 2-4(即會根據(jù)加入的Job自動在 2-4 個線程之間自動調(diào)整)。
?????? 三個AddJob按鈕分別是增加 高、普通、低優(yōu)先級的Job,加入后可以在日志中查看其運行的順序。
?????? 兩個CancelJob按鈕分別是從前面、后面的JobIndex取消Job(實際上支持任和有效的JobIndex)。
??????
6.??特別說明
6.1.?????IFThreadPoolCallBack 回調(diào)的同步
Pool 調(diào)用 IFThreadPoolCallBack 接口的各個方法時,為了性能上的考慮,沒有加鎖進行同步。使用時如果有需要,最好自行同步。
6.2.?????單任務(wù)的暫停、繼續(xù) VS Pool的暫停、繼續(xù)
在開發(fā)過程中,考慮過是提供單任務(wù)的暫停、繼續(xù) 還是 整個Pool 的暫停、繼續(xù),考慮到目前的需求,暫時只支持整個Pool的暫停,免得過分復(fù)雜,如有需要,大家可以自行參考CancelJob實現(xiàn)。
6.3.?????可能存在的Bug和解決方法
在開發(fā)的過程中,對各個部分都進行了詳細的分析、考慮和測試,應(yīng)該沒有較大的Bug。目前只想到一種在極端情況下,可能會造成的Bug,特提出以免各位踩雷。
描述:因為是多線程的代碼,所以在極端情況下,可能出現(xiàn)SubmitJob函數(shù)尚未返回,對應(yīng)的Job就執(zhí)行完畢(或 Initialize失敗直接返回)的情況。此時如果調(diào)用端采用了將 *pOutJobIndex保存起來,在 OnJobEnd 中清除的邏輯,可能會因為 OnJobEnd 找不到對應(yīng)的JobIndex 而出現(xiàn)邏輯錯誤。
解決方法:在調(diào)用 SubmitJob 的代碼和OnJobEnd 的回調(diào)代碼中,使用相同的鎖機制保證即可(這個也應(yīng)該由調(diào)用者來保證)。
源碼和示例程序的下載地址請參見本人的資源列表(可能需要等一段CSDN刷新的時間)。
http://download.csdn.net/detail/fishjam/5106672
總結(jié)
以上是生活随笔為你收集整理的Windows下一个比较完美的线程池实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VC++中进程间相互通信的十一种方法
- 下一篇: 客户端与服务器持续同步解析(轮询,com