Drecik學習經驗分享
轉載請註明出處:http://blog.csdn.net/drecik__/article/details/8194020
Windows線程提供了一個線程池機制來簡化線程的創建,銷燬以及日常管理,避免了線程頻繁創建和銷燬的開銷。
一下介紹的與現場池有關的函數是新的線程池API,只能運行在Windwos Vista及以後版本。
這些線程池函數可以幫助我們做以下事情:
- 以異步的方式來調用一個函數
- 每隔一段時間調用一個函數
- 當內核對象出發的時候調用一個函數
- 當異步I/O請求完成的時候調用一個函數
1. 以異步的方式來調用一個函數
有兩種方式實現該過程,下面分別講述:
- 第一種方法,首先定義一個具有以下原型的函數:
爲了讓自己定義好的函數加入到線程池中,讓線程執行我們的函數,我們需要向線程池提交請求:typedef VOID (NTAPI *PTP_SIMPLE_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, // 用戶回調函數終止操作,我也沒看懂,傳NULL吧,一般操作都夠用了; PVOID Context // 在加入線程池的時候由用戶傳入的參數; );
加入成功後,線程池就會有線程來執行我們的函數。BOOL TrySubmitThreadpoolCallback( PTP_SIMPLE_CALLBACK pfns, // 自己定義的函數; PVOID pv, // 傳給自己定義函數的Context參數; PTP_CALLBACK_ENVIRON pcbe // 用來自己創建線程池,使用默認線程池傳入NULL; );
- 第二種方法:由於每一次調用TrySubmitThreadpoolCallback系統內部的都會以我們的名義分配一個工作項,如果打算提交大量的工作項,那麼可以處於性能和內存使用的考慮,創建工作性一次,然後分多次提交它會更好。
我們使用CreateThreadpoolWork創建一個工作項:PTP_WORK CreateThreadpoolWork( PTP_WORK_CALLBACK pfnwk, // 工作項調用的函數; PVOID pv, // 傳給調用函數的參數; PTP_CALLBACK_ENVIRON pcbe ); // PTP_WORK_CALLBACK函數原型; typedef VOID (NTAPI *PTP_WORK_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, // 上面函數傳進來的pv參數; PTP_WORK Work // 關聯的工作項; ); // 向線程池提交一個請求的時候調用下面函數; // 提交成功後,線程池會有線程來執行我們創建的函數; VOID SubmitThreadpoolWork( PTP_WORK pwk // 工作項; );
多次提交唯一的缺點就是每一次傳給自定義函數的Context值都相同,如果需要不同的值只能使用TrySubmitThreadpoolCallback函數。
如果我們有另外一個線程,該線程負責取消已經提交的工作項,或者等待工作項處理完畢而需要將自己掛起,則需要調用下面函數:
// 如果工作項未提交,函數就立即返回不執行任何操作; // 第二個參數如果爲TRUE,那麼該函數會試圖取消先前提交的那個工作項; // 如果線程池中的線程正在處理那個工作項,該函數會一直等待到工作項完成後才返回; // 如果已經提交的工作項尚未被任何線程處理,那麼會將它標記會取消,並立即返回,這樣就不會被執行; // 如果爲FALSE,那麼將會掛起,知道指定工作項的處理已經完成,而且線程池處理該工作項的線程都已經被收回位置; // 如果用一個PTP_WORK對象提交了多個工作項,傳給第二個參數爲FALSE,就會等待所有工作項完成; // 如果爲TRUE,只會等待當前正在運行的工作項; VOID WaitForThreadpoolWorkCallbacks( PTP_WORK pwk, // 待取消或等待的工作項; BOOL fCancelPendingCallbacks ); // 最後一個函數關閉創建的工作項; VOID CloseThreadpoolWork( PTP_WORK pwk );
2. 每個一段時間調用一個函數
① 首先我們必須定義一個如下原型的回調函數:② 創建一個PTP_TIMER對象:typedef VOID (NTAPI *PTP_TIMER_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, // 傳入的參數; PTP_TIMER Timer // 關聯的TIME指針; );
③ 註冊計時器:PTP_TIMER CreateThreadpoolTimer( PTP_TIMER_CALLBACK pfnti, // 自己定義的函數指針; PVOID pv, // 傳入自己定義的函數的參數; PTP_CALLBACK_ENVIRON pcbe );
設置了計時器之後,還可以調用SetThreadpoolTimer來對計時器進行修改,也可以在pftDueTimer傳入NULL表示停止調用回調函數。// 其中開始時間傳入負值表示從現在開始多長時間第一次觸發; // 如果爲正值,表示一個絕對時間,從1600年1月1日開始計算; // 單位都爲100納秒; VOID SetThreadpoolTimer( PTP_TIMER pti, // 創建的計時器對象; PFILETIME pftDueTime, // 開始時間; DWORD msPeriod, // 每隔多少時間觸發一次,只觸發一次傳入0; DWORD msWindowLength // 該參數可以指定在msPeriod+msWindowLength這一時間段內觸發,防止衝突; );
通過IsThreadpoolTimerSet來判斷某個計時器是否被設置。
最後兩個函數WaitForThreadpoolTimerCallbacks和CloseThradpoolTimer與之前討論過的類似,不在討論。
3. 在內核對象出發時調用一個函數
這些線程池操作基本上都大同小異,所以有些參數不在解釋:
// 回調函數; // 第四個參數表示事件觸發的類型,WAIT_OBJECT_0:關聯的事件觸發; // WAIT_TIMEOUT:等待超時,WAIT_ABANDONED_0:互斥量遺棄(參考以前的博文); VOID (NTAPI *PTP_WAIT_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult ); PTP_WAIT CreateThreadpoolWait( PTP_WAIT_CALLBACK pfnwa, PVOID pv, PTP_CALLBACK_ENVIRON pcbe ); // 關聯內核對象; // 可多次調用關聯多個事件,但其中一個觸發就會調用回調函數; // 第三個參數表示等待時間,0表示不等待,NULL表示無限等待,正數表示絕對時間,負數相對時間; VOID SetThreadpoolWait( PTP_WAIT pwa, HANDLE h, // 內核對象; PFILETIME pftTimeout );
一旦調用回調函數,對應的等待項將不活躍,必須重新關聯該內核對象。
可以在SetThreadpoolWait事件傳入NULL表示將該等待項從內存池中移除。
最後WaitForThreadpoolWaitCallbacks等待等待項完成,CloseThreadpoolWait釋放等待項內存。
最好不要使用PulseEvent觸發事件,因爲無法保證線程池正好在等待該事件。
4. 在異步I/O請求完成時調用一個函數
同樣還有WaitForThreadpoolIoCallbacks來等待請求的完成。typedef VOID (WINAPI *PTP_WIN32_IO_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, PVOID Overlapped, // 異步IO傳入的OVERLAPPED結構指針; ULONG IoResult, // 是否有錯誤,NO_ERROR表示沒有錯誤; ULONG_PTR NumberOfBytesTransferred, // 傳輸的字節; PTP_IO Io ); PTP_IO CreateThreadpoolIo( HANDLE fl, // 設備句柄; PTP_WIN32_IO_CALLBACK pfnio, // 回調函數; PVOID pv, PTP_CALLBACK_ENVIRON pcbe ); // 每次發送異步I/O請求必須與線程池關聯; VOID StartThreadpoolIo( PTP_IO pio ); // 在發出請求之後讓線程停止調用回調函數; // 如果發送請求的時候調用失敗,則仍然必須調用下面函數; VOID CancelThreadpoolIo( PTP_IO pio ); // 在關閉設備對象的時候,解除與內存池的關聯; VOID CloseThreadpoolIo( PTP_IO pio );