《Windows核心編程系列》十一談談Windows線程池

轉自:http://blog.csdn.net/ithzhang/article/details/8373243

   Windows線程池

 

     上一篇博文我們介紹了IO完成端口。得知IO完成端口可以非常智能的分派線程。但是IO完成端口僅對等待它的線程進行分派,創建和銷燬線程的工作仍然需要我們自己來做。

 

     我們自己也可以創建線程,但是涉及到線程的編碼操作比較複雜,容易出現差錯。爲了簡化程序員的工作,Windows提供了一個線程池機制來簡化線程的創建、銷燬以及日常管理。這個新線程池可能不適用於所有的情況,但大多數情況下它都能夠滿足我們的需要。

 

這個線程池能夠幫助我們做一下事情:

 

    一:以異步的方式調用一個函數。

 

    二:每隔一段時間調用一個函數。

 

    三:當內核對象觸發時調用一個函數。

 

    四:當異步IO請求完成時調用一個函數。

 

我們將在後面一一介紹上面各項。

 

 

一:以異步方式調用函數。

 

    讓線程池執行的函數需要遵循一下原型:

 

    

  1. VOID NTAPI ThreadFunc(  
  2.   
  3.        PTP_CALLBACK_INSTANCE pInstance,  
  4.   
  5.        PVOID pvContext);  
VOID NTAPI ThreadFunc(

       PTP_CALLBACK_INSTANCE pInstance,

       PVOID pvContext);


    定義了線程池線程入口函數,就需要提交請求讓線程池執行該函數:

 

 

  1. BOOL TrySubmitThreadpoolCallback(  
  2.   
  3.      PTP_SIMPLE_CALLBACK pfnCallback,  
  4.   
  5.      PVOID pvContext,  
  6.   
  7.      PTP_CALLBACK_ENVIRON pche);  
BOOL TrySubmitThreadpoolCallback(

     PTP_SIMPLE_CALLBACK pfnCallback,

     PVOID pvContext,

     PTP_CALLBACK_ENVIRON pche);


該函數將一個工作項添加到線程池隊列中。若調用成功,則返回true。否則返回false

 

pfnCallback表示線程池線程入口函數。即我們上面定義的函數。

 

pvContext是傳給線程入口函數的參數。

 

pche可以先傳給它NULL。在後面我們還會有詳細的介紹。

 

    當我們提交一個請求後,線程池就會創建一個默認的線程池並讓線程池的一個線程來調用回調函數。並不需要我們手動調用CreateThread。當線程從入口函數返回時,並不會銷燬而是返回到線程池。線程池會不斷重複使用各個線程,而不會頻繁銷燬和新建線程。這顯著的提高了性能。

 

    在某些情況下,如內存不足時TrySubmitThreadpoolCallback可能會失敗。第一次調用TrySubmitThreadpoolCallback時,系統會在內部分配一個工作項。如果打算提交大量的工作項,出於性能和內存使用方面的考慮,應該手動創建工作項然後多次提交它。

 

    下面的函數創建一個工作項:

 

 

  1. PTP_WORK CreateThreadpoolWork(  
  2.   
  3.      PTP_WORK_CALLBACK pfnWorkHandler,  
  4.   
  5.      PVOID pvContext,  
  6.   
  7.      PTP_CALLBACK_ENVIRON pche);  
PTP_WORK CreateThreadpoolWork(

     PTP_WORK_CALLBACK pfnWorkHandler,

     PVOID pvContext,

     PTP_CALLBACK_ENVIRON pche);


 

    pfnWorkHandler是一個函數指針,當線程池中的線程最終對工作項進行處理時會調用該函數。該函數必須遵循一下函數原型:

 

 

  1. VOID CALLBACK WorkCallback(  
  2.   
  3. PTP_CALLBACK_INSTANCE Instance,  
  4.   
  5. PVOID Context,  
  6.   
  7. PTP_WORK Work);  
VOID CALLBACK WorkCallback(

PTP_CALLBACK_INSTANCE Instance,

PVOID Context,

PTP_WORK Work);


 

     pvContext是傳給pfnWorkHandler的參數。

 

     我們可以調用SubmitThreadpoolWork向線程池提交一個請求:

 

    

  1. VOID SubmitThreadpoolWork(PTP_WORK pWork);  
 VOID SubmitThreadpoolWork(PTP_WORK pWork);

 

     如果我們項取消已經提交的工作項或是等待工作項處理完畢。可以調用以下函數:

 

 

  1. VOID WaitForThreadpoolWorkCallbacks(  
  2.   
  3. PTP_WORK pWork,  
  4.   
  5. BOOL bCancelPendingCallbacks);  
VOID WaitForThreadpoolWorkCallbacks(

PTP_WORK pWork,

BOOL bCancelPendingCallbacks);


 

此函數將線程掛起,直到工作項處理完畢。

 

pWork指向工作項。此工作項可以是CreateThreadpoolWorkSubmitThreadpoolWork來創建和提交的。如果工作項尚未被提交,那麼等待函數立即返回。

 

如果傳入truebCancelPendingCallbacks,那麼WaitForThreadpoolWorkCallbacks會試圖取消pWork標識的工作項。如果線程正在處理此工作項,則不會取消,而等待工作項處理完畢後返回。如果工作項還未被處理,函數會將此工作項標記爲已取消,然後立即返回。

 

如果傳入falsebCancelPendingCallbacks那麼WaiForThreadpoolWorkCallbacks會將線程掛起,直到工作項處理完畢。

 

如果用一個PTP_WORK提交了多個工作項,傳給bCancelPendingCallbacksfalse,那麼等待函數會等待所有的工作項都被處理完畢。

 

當不需要一個工作項時,可以調用CloseThreadpoolWork

 

  1. VOID CloseThreadpoolWork(PTP_WORK pwk);  
VOID CloseThreadpoolWork(PTP_WORK pwk);


 

 

看例子:該例展示瞭如何使用線程池的工作項。

 

點擊開始提交四次同一個工作項。

 

當回調函數返回時,程序將向程序發送自定義的TASK_COMPELETED消息。然後該消息處理函數將項下拉列表添加一項。

 

MFC中手動添加消息需要一下步驟:

 

1:定義消息:如#defined TASK_COMPELETED WM_USER+1

 

    2:定義消息處理函數原型:

 

 

  1. afx_msg LRESULT OnTaskCompeleted(WPARAM wparam,LPARAM lparam);  
afx_msg LRESULT OnTaskCompeleted(WPARAM wparam,LPARAM lparam);


 

    3:調用PostMessage發送消息。

 

  1. PostMessage(this,TASK_COMPELETED,0,(LPARAMm_CurrentTask);  
PostMessage(this,TASK_COMPELETED,0,(LPARAMm_CurrentTask);


 

    4:在消息映射表中添加消息和消息處理函數的映射:

 

代碼如下:

 

 

開始按鈕消息處理函數:

  1. void CThreadpoolexap1Dlg::OnBnClickedBtnStart()  
  2. {  
  3.     m_WorkItem=CreateThreadpoolWork(ThreadFunc,this,NULL);  
  4.     if(m_WorkItem==NULL)  
  5.     {  
  6.         MessageBox(TEXT("工作項創建失敗"));  
  7.         return ;  
  8.     }  
  9.     // TODO: 在此添加控件通知處理程序代碼   
  10.     SubmitThreadpoolWork(m_WorkItem);  
  11.     SubmitThreadpoolWork(m_WorkItem);  
  12.     SubmitThreadpoolWork(m_WorkItem);  
  13.     SubmitThreadpoolWork(m_WorkItem);  
  14. }  
void CThreadpoolexap1Dlg::OnBnClickedBtnStart()
{
	m_WorkItem=CreateThreadpoolWork(ThreadFunc,this,NULL);
	if(m_WorkItem==NULL)
	{
		MessageBox(TEXT("工作項創建失敗"));
		return ;
	}
	// TODO: 在此添加控件通知處理程序代碼
	SubmitThreadpoolWork(m_WorkItem);
	SubmitThreadpoolWork(m_WorkItem);
	SubmitThreadpoolWork(m_WorkItem);
	SubmitThreadpoolWork(m_WorkItem);
}

線程入口函數:

  1. VOID NTAPI CThreadpoolexap1Dlg::ThreadFunc( PTP_CALLBACK_INSTANCE Instance,PVOID Context,PTP_WORK Work )  
  2. {  
  3.     CThreadpoolexap1Dlg*pdlg=(CThreadpoolexap1Dlg*)Context;  
  4.     InterlockedIncrement(&pdlg->m_CurrentTask);  
  5.     DWORD num=pdlg->m_CurrentTask;  
  6.     CString s;  
  7.     s.Format(TEXT("【%d】任務%d開始運行!"),GetCurrentThreadId(),pdlg->m_CurrentTask);  
  8.     pdlg->m_list.InsertString(-1,s);  
  9.     pdlg->PostMessage(TASK_COMPELETED,0,(LPARAM)num);  
  10. }  
VOID NTAPI CThreadpoolexap1Dlg::ThreadFunc( PTP_CALLBACK_INSTANCE Instance,PVOID Context,PTP_WORK Work )
{
	CThreadpoolexap1Dlg*pdlg=(CThreadpoolexap1Dlg*)Context;
	InterlockedIncrement(&pdlg->m_CurrentTask);
	DWORD num=pdlg->m_CurrentTask;
	CString s;
	s.Format(TEXT("【%d】任務%d開始運行!"),GetCurrentThreadId(),pdlg->m_CurrentTask);
	pdlg->m_list.InsertString(-1,s);
	pdlg->PostMessage(TASK_COMPELETED,0,(LPARAM)num);
}


自定義消息處理函數:

  1. LRESULT CThreadpoolexap1Dlg::OnTaskCompeleted( WPARAM wparam,LPARAM lparam )  
  2. {  
  3.     DWORD num=(DWORD)lparam;  
  4.     CString s;  
  5.     s.Format(TEXT("任務 [%d]執行完畢!"),num);  
  6.     m_list.InsertString(-1,s);  
  7.     return 0;  
  8. }  
LRESULT CThreadpoolexap1Dlg::OnTaskCompeleted( WPARAM wparam,LPARAM lparam )
{
	DWORD num=(DWORD)lparam;
	CString s;
	s.Format(TEXT("任務 [%d]執行完畢!"),num);
	m_list.InsertString(-1,s);
	return 0;
}

 

 

執行結果:

 


   多次點擊開始鍵發現確實是還是那四個線程在執行。

 

修改代碼:提交六個工作項:

 


        多次點擊開始參與執行的仍然是原來的線程。說明在線程池中線程是重用的。

 

 


情形二:每隔一段時間調用一個函數

 

有時候應用程序需要在某些時間執行某些任務。Windows提供了可等待計時器對象,它使我們我們可以非常方便的得到一個時間通知。我們可以爲每個需要執行基於時間的任務創建一個可等待的計時器對象,但這是不必要的。線程池函數爲我們解決了這些事情。

 

爲了將一個工作項安排在某個時間執行,我們必須定義一個回調函數。該函數會在某個時刻被調用。回調函數原型爲:

 

 

  1. VOID CALLBACK TimeoutCallback(  
  2.   
  3.     PTP_CALLBACK_INSTANCE pInstance,  
  4.   
  5.     PVOID pvContext,  
  6.   
  7.     PTP_TIMER pTimer);  
VOID CALLBACK TimeoutCallback(

    PTP_CALLBACK_INSTANCE pInstance,

    PVOID pvContext,

    PTP_TIMER pTimer);


 

然後調用下面的函數來通知線程池應在何時調用我們的函數:

 

 

  1. PTP_TIMER CreateThreadpoolTimer(  
  2.   
  3.      PTP_TIMER_CALLBACK pfnTimerCallback,  
  4.   
  5.      PVOID pvContext,  
  6.   
  7.      PTP_CALLBACK_ENVIRON pcbe);  
PTP_TIMER CreateThreadpoolTimer(

     PTP_TIMER_CALLBACK pfnTimerCallback,

     PVOID pvContext,

     PTP_CALLBACK_ENVIRON pcbe);


 

這個函數與前面介紹的CreateThreadpoolWork相似。

 

pfnTimerCallback是一個函數指針。指向前面介紹的回調函數TimeroutCallback。每當線程池調用pfnTimerCallback指向的函數時會將pvContext傳給它,並傳給pTimer一個由CreateThreadpoolTimer返回的計時器對象指針。

 

pvContext爲傳給回調函數參數。

 

CreateThreadpoolTimer返回計時器對象。該計時器對象由CreateThreadpoolTimer函數創建並返回。

 

當我們想要向線程池註冊計時器時,應該調用SetThreadpoolTimer

 

 

  1. VOID SetThreadpoolTimer(  
  2.   
  3.      PTP_TIMER pTimer,  
  4.   
  5.      PFILETIME pftDueTime,  
  6.   
  7.      DWORD msPeriod,  
  8.   
  9.      DWORD msWindowLength);  
VOID SetThreadpoolTimer(

     PTP_TIMER pTimer,

     PFILETIME pftDueTime,

     DWORD msPeriod,

     DWORD msWindowLength);


 

pTimer用來標識CreateThreadpoolTimer返回的計時器對象。

 

pftDueTime表示第一次調用回調函數是什麼時候。傳入一個負值表示一個相對時間。該時間相對於調用SetThreadpoolTimer的時間。傳入-1表示立即調用。傳入的正值以100ns爲單位,從1600年的11日開始計算。

msPeriod表示調用回調函數的時間間隔,傳入0表示只調用1次。

 

msWindowLength用來給回調函數的執行增加一些隨機性。這使得回調函數會在當前設定的時間到當前設定的觸發時間加上msWindowLength設定的時間之間觸發。這對於多個計時器來說非常有用。這可以避免多個計時器間的衝突。

 

在設置計時器之後還可以再次調用SetThreadpoolTimer來對計時器進行修改。同時也可以調用IsThreadpoolSet來確定某個計時器是否已經被設置(即pfnDueTime不爲NULL)。

 

 

  1. BOOL IsThreadpoolTimerSet(PTP_TIMER pti);  
BOOL IsThreadpoolTimerSet(PTP_TIMER pti);


 

    最後我們可以通過WaitForThreadpoolTimerCallbacks來等待一個計時器完成。調用CloseThreadpoolTimer來釋放計時器。它們與前面介紹的WaitForThreadpoolWorkCloseThreadpoolWork相似。

 

    例子:該例展示瞭如何使用線程池計時器函數。程序啓動時會將m_TimeToLeft設置爲10。接着調用CreateThreadpoolTimer函數來創建線程池函數計時器。並將它傳給SetThreadTimer。告訴線程池從第一秒開始,以後每隔一秒調用一次回調函數。注意:由於回調函數是由線程池的線程調用,因此在類中定義時必須是static的。另外之所有回調函數會被多次調用,是因爲系統內部每個一段時間會將一個工作項添加到隊列中。對於這些工作項線程池可能調用多個線程進行處理。因此要注意採取必要的線程同步機制。本例中採用InterlockedDecrement函數對m_TimeToLeft進行同步。要注意InterlockedDecrement的參數是ULONG類型。傳入負值會有問題。


.h文件定義:

  1. <SPAN style="FONT-SIZE: 18px">   public:  
  2.     PTP_TIMER m_pTimer;  
  3.     CString m_display;  
  4.     ULONG m_TimeToLeft;  
  5.    public:  
  6.     static VOID CALLBACK TimerCallbackFunc(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_TIMER pTimer);  
  7.     afx_msg void OnBnClickedBtnOk();</SPAN>  
   public:
   	PTP_TIMER m_pTimer;
   	CString m_display;
   	ULONG m_TimeToLeft;
   public:
   	static VOID CALLBACK TimerCallbackFunc(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_TIMER pTimer);
   	afx_msg void OnBnClickedBtnOk();


 

初始化代碼:

  1. <SPAN style="FONT-SIZE: 18px">   // TODO: 在此添加額外的初始化代碼   
  2.     m_pTimer=CreateThreadpoolTimer(TimerCallbackFunc,this,NULL);  
  3.     if(m_pTimer==NULL)  
  4.     {  
  5.         MessageBox(TEXT("PTP_TIMER創建失敗!!"));  
  6.         //return false;   
  7.     }  
  8.     FILETIME filetime;  
  9.     LARGE_INTEGER li;  
  10.     li.QuadPart=-(10000000);  
  11.        filetime.dwHighDateTime=li.HighPart;  
  12.     filetime.dwLowDateTime=li.LowPart;  
  13.     SetThreadpoolTimer(m_pTimer,&filetime,1000,0);  
  14.     CString s;  
  15.     s.Format(TEXT("你還有[%d]s時間拯救地球!!"),10);  
  16.     m_display=s;  
  17.     UpdateData(false);</SPAN>  
   // TODO: 在此添加額外的初始化代碼
   	m_pTimer=CreateThreadpoolTimer(TimerCallbackFunc,this,NULL);
   	if(m_pTimer==NULL)
   	{
   		MessageBox(TEXT("PTP_TIMER創建失敗!!"));
   		//return false;
   	}
   	FILETIME filetime;
   	LARGE_INTEGER li;
   	li.QuadPart=-(10000000);
       filetime.dwHighDateTime=li.HighPart;
   	filetime.dwLowDateTime=li.LowPart;
   	SetThreadpoolTimer(m_pTimer,&filetime,1000,0);
   	CString s;
   	s.Format(TEXT("你還有[%d]s時間拯救地球!!"),10);
   	m_display=s;
   	UpdateData(false);


按鈕消息響應函數:

  1. <SPAN style="FONT-SIZE: 18px">   void CThreadpoolExap2Dlg::OnBnClickedBtnOk()  
  2.    {  
  3.     // TODO: 在此添加控件通知處理程序代碼   
  4.     static bool IsClicked=false;  
  5.     if(IsClicked==false)  
  6.     {  
  7.         CloseThreadpoolTimer(m_pTimer);  
  8.         IsClicked=true;  
  9.     }  
  10.     if(m_TimeToLeft>1)  
  11.     {  
  12.           
  13.         MessageBox(TEXT("你已拯救地球!!"));  
  14.      
  15.     }  
  16.     else  
  17.     {  
  18.           
  19.           
  20.         MessageBox(TEXT("地球已毀滅!!"));  
  21.     }</SPAN>  
   void CThreadpoolExap2Dlg::OnBnClickedBtnOk()
   {
   	// TODO: 在此添加控件通知處理程序代碼
   	static bool IsClicked=false;
   	if(IsClicked==false)
   	{
   		CloseThreadpoolTimer(m_pTimer);
   		IsClicked=true;
   	}
   	if(m_TimeToLeft>1)
   	{
   		
   		MessageBox(TEXT("你已拯救地球!!"));
   
   	}
   	else
   	{
   		
   		
   		MessageBox(TEXT("地球已毀滅!!"));
   	}


線程池回調函數:

 

  1. <SPAN style="FONT-SIZE: 18px">   VOID CALLBACK CThreadpoolExap2Dlg::TimerCallbackFunc( PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_TIMER pTimer )  
  2.    {  
  3.     CThreadpoolExap2Dlg*pdlg=(CThreadpoolExap2Dlg*)pvContext;  
  4.       
  5.     DWORD temp=pdlg->m_TimeToLeft;  
  6.       
  7.     CString s;  
  8.     s.Format(TEXT("你還有[%d]s時間拯救地球!!"),temp-1);  
  9.     pdlg->SetDlgItemTextW(IDC_STATIC_DISPLAY,s);  
  10.     //pdlg->m_display=s;   
  11.     //pdlg->UpdateData(true);   
  12.       
  13.     if(temp==1)  
  14.     {  
  15.         CloseThreadpoolTimer(pdlg->m_pTimer);  
  16.         pdlg->MessageBox(TEXT("地球已毀滅!!"));  
  17.         return ;  
  18.           
  19.     }  
  20.     InterlockedDecrement(&pdlg->m_TimeToLeft);  
  21.    }</SPAN>  
   VOID CALLBACK CThreadpoolExap2Dlg::TimerCallbackFunc( PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_TIMER pTimer )
   {
   	CThreadpoolExap2Dlg*pdlg=(CThreadpoolExap2Dlg*)pvContext;
   	
   	DWORD temp=pdlg->m_TimeToLeft;
   	
   	CString s;
   	s.Format(TEXT("你還有[%d]s時間拯救地球!!"),temp-1);
   	pdlg->SetDlgItemTextW(IDC_STATIC_DISPLAY,s);
   	//pdlg->m_display=s;
   	//pdlg->UpdateData(true);
   	
   	if(temp==1)
   	{
   		CloseThreadpoolTimer(pdlg->m_pTimer);
   		pdlg->MessageBox(TEXT("地球已毀滅!!"));
   		return ;
   		
   	}
   	InterlockedDecrement(&pdlg->m_TimeToLeft);
   }


執行結果:

 

 

 

情形三:在內核對象觸發時調用一個函數:

 

在實際使用中我們會發現我們會經常的等待一個內核對象被觸發,觸發後等待線程又會進入下一輪循環繼續等待。Windows線程池提供了一些機制可以簡化我們的工作。

 

如果我們想讓內核對象被觸發時執行某函數。需要進行以下步驟:

 

首先編寫一個回調函數,它是內核對象被觸發時被調用的函數。需要滿足一下原型:

 

VOID CALLBACK WaitCallback(

    PTP_CALLBACK_INSTANCE pInstance,

    PVOID Context,

    PTP_WAIT Wait,

    TP_WAIT_RESULT WaitResult);

 

然後創建CreateThreadpoolWait來將一個內核對象綁定到線程池:


VOID SetThreadpoolWait(

   PTP_WAIT pWaitItem,

   HANDLE hObject,

   PFILETIME pftTimeout);

 

pWaitItem用來標識CreateTheadpoolWait返回的對象。

 

hObject用來標識內核對象。當此對象被觸發時,回調函數會被調用。

 

pftTimeout用來表示線程池最長應該花多少時間來等待內核對象被觸發。傳入0表示不用等待。傳入負值表示相對時間傳NULL表示無限長的時間。

 

線程池內部會讓一個線程調用WaitForMultipleOBjecs函數。傳入SetThreadpoolWait函數註冊的一組句柄,並傳入falsebWaitAll參數。當任何一個內核對象被觸發時,線程池就會被喚醒。

 

當內核對象被觸發或是超出等待時間時,線程池的某個線程就會調用我們的回調函數。

 

WaitResult用來表示WaitCallback被調用的原因。它可以是以下值:

 

WAIT_OBJECT_0        超時之前有對象被觸發。

 

WAIT_TIMEOUT      由於超時導致回調函數被觸發。

 

WAIT_ABANDONED_0   如果傳入的內核對象是互斥量且被遺棄。回調函數將收到這個值。

 

一旦線程池調用了我們的回調函數,對應的等待項將進入不活躍狀態。所謂不活躍狀態:如果想讓回調函數在同一個內核對象被觸發時再次被調用,我們需要調用SetThreadpoolWait來再次註冊。

 

最後我們同樣可以等待一個等待項完成。這可以調用WaitForThreadpoolWaitCallbacks。還可以調用CloseThreadpoolWait來釋放一個等待項的內存。

 

注意:不要讓回調函數調用WaitForThreadpoolWork並將自己的工作項作爲參數傳入,這會導致死鎖。

 

 

 

情形四:在異步IO完成時調用一個函數

 

我們在上一篇博文中介紹瞭如何使用IO完成端口來高效的執行異步IO操作,也介紹瞭如何創建一個線程池並讓其中的線程等待IO完成端口。這裏我們將介紹線程池如何管理線程的創建和銷燬。

 

在打開一個關聯起來文件或設備時,我們必須現將該設備與線程池的IO完成端口,然後告訴線程池在異步IO完成時應該調用哪個函數。

 

首先我們需要定義回調函數,它需要滿足一下原型:

 

 

  1. VOID CALLBACK OverlappedCompletionRoutine(  
  2.   
  3.     PTP_CALLBACK_INSTANCE pInstance,  
  4.   
  5.     PVOID pvContext,  
  6.   
  7.     PVOID pOverlapped,  
  8.   
  9.     ULONG IoResult;  
  10.   
  11.     ULONG_PTR NumberOfBytesTransferred,  
  12.   
  13.     PTP_IO  pIo);  
VOID CALLBACK OverlappedCompletionRoutine(

    PTP_CALLBACK_INSTANCE pInstance,

    PVOID pvContext,

    PVOID pOverlapped,

    ULONG IoResult;

    ULONG_PTR NumberOfBytesTransferred,

    PTP_IO  pIo);


 

當一個IO操作完成時此回調函數會被調用並得到一個指向OVERLAPPED結構的指針。此結構是我們在調用ReadFileWriteFile時傳入的。

 

IoResult表示IO異步操作的執行結果。如果IO請求成功,將傳給回調函數NO_ERROR

 

NumberOfBytesTransferred參數傳入已傳輸的字節數。

 

pIo傳入指向線程池IO項的指針。馬上介紹。

 

pInstance後面會有介紹。

 

定義好回調函數後,我們就需要調用CreateThreadpoolIo來創建一個線程池IO對象。

 

 

  1. PTP_IO CreateThreadpoolIo(  
  2.   
  3.     HANDLE hDevice,  
  4.   
  5.     PTP_WIN32_IO_CALLBACK pfnIoCallback,  
  6.   
  7.     PVOID pvContext,  
  8.   
  9.     PTP_CALLBACK_ENVIRON pcbe);  
PTP_IO CreateThreadpoolIo(

    HANDLE hDevice,

    PTP_WIN32_IO_CALLBACK pfnIoCallback,

    PVOID pvContext,

    PTP_CALLBACK_ENVIRON pcbe);


 

hDevice是與IO對象相關聯的設備句柄。

 

pfnIoCallback是前面我們介紹的回調函數指針。

 

pvContext當然是傳給回調函數的參數。

 

IO對象創建好之後,我們就可以通過下面的函數來將潛入在IO項的設備與IO完成端口關聯起來。

 

 

  1. VOID StartThreadpoolIo(PTP_IO pio);  
VOID StartThreadpoolIo(PTP_IO pio);


 

關聯之後我們就可以調用ReadFileWriteFile了。此後當異步IO請求完成後,回調函數將會被調用。

 

此外我們還可以調用以下函數來停止線程池調用回調函數,此後回調函數將不會被調用:

 

 

  1. VOID CancelThreadpoolIo(PTP_IO pio);  
VOID CancelThreadpoolIo(PTP_IO pio);


 

CloseThreadpoolIo將取消設備與線程池的關聯:

 

 

  1. VOID CloseHandlepoolIo(PTP_IO pio);  
VOID CloseHandlepoolIo(PTP_IO pio);


 

WaitForThreadpoolIoCallbacks將等待一個待處理的IO請求我完成。

 

 

  1. VOID WaitForThreadpoolIoCallback(  
  2.   
  3.     PTP_IO pio,  
  4.   
  5.     BOOL bCancelPendingCallbacks);  
VOID WaitForThreadpoolIoCallback(

    PTP_IO pio,

    BOOL bCancelPendingCallbacks);


 

如果傳給bCancelPendingCallbacks的值爲true,那麼當請求完成時,回調函數不會被調用。

 

 

 

           對線程池進行定製

 

 

     在調用CreateThreadpoolWorkCreateThreadpoolTimerCreateThreadpoolWaitCreateThreadpoolIo時,有一個PTP_CALLBACK_ENVIRON類型的參數。如果傳給它NULL則表示我們會將工作項添加到默認的線程池中。一般情況下默認的線程池能夠滿足大多數情況下的要求。

 

     如果我們想定製我們自己的線程池,可以調用CreateThreadpool來創建新線程池:

 

 

  1. PTP_POOL CreateThreadpool(PVOID reserved);  
PTP_POOL CreateThreadpool(PVOID reserved);


 

Reserved是保留的,傳入NULL即可。

 

該函數返回一個PTP_POOL值,它表示新創建的線程。

 

    此後我們就可以設置線程池中的最大線程和最小線程了。默認的線程池中線程最少爲1個,最多爲500

 

  1. void SetThreadpoolThreadMinimum(PTP_POOL pThreadpool,DWORD cthrdMin);  
  2.   
  3.   
  4.   
  5. void SetThreadpoolThreadMaximum(PTP_POOL pThreadpool,DWORD cthrdMin);  
void SetThreadpoolThreadMinimum(PTP_POOL pThreadpool,DWORD cthrdMin);



void SetThreadpoolThreadMaximum(PTP_POOL pThreadpool,DWORD cthrdMin);


 

    這僅僅是告訴線程池最大和最少線程個數。但實際使用中,線程池會非常智能的決定創建或是銷燬線程。只要這樣做提高性能有益。

 

當我們不需要自己定製的線程池時可以調用CloseThreadpool銷燬。

 

 

  1. VOID CloseThreadpool(PTP_POOL pThreadpool);    
VOID CloseThreadpool(PTP_POOL pThreadpool);  


 

    調用此函數後線程池所有的線程都被終止。線程池隊列中所有尚未開始處理的項將被取消。

 

    創建了線程池之後,並對線程池進行一定的配置後。我們就可以初始化線程池了,以便構造出一個回調環境。回調環境是一個PTP_CALLBACK_ENVIRON結構。該結構中包含了線程池、清理組等字段。我們可以調用InitializeThreadpoolEnvironment對其進行初始化:

 

 

  1. VOID InitializeThreadpoolEnvironment(  
  2.   
  3. PTP_CALLBACK_ENVIRON pcbe);  
VOID InitializeThreadpoolEnvironment(

PTP_CALLBACK_ENVIRON pcbe);


 

    該函數會將回調環境的版本字段置爲1,其餘字段爲0.

 

    初始化回調環境後,還需要將其與線程池進行關聯:

 

 

  1. VOID SetThreadpoolCallbackpoo(  
  2.   
  3.       PTP_CALLBACK_ENVIRON pche,  
  4.   
  5.       PTP_POOL pThreadPool);  
VOID SetThreadpoolCallbackpoo(

      PTP_CALLBACK_ENVIRON pche,

      PTP_POOL pThreadPool);


 

該函數將回調環境的線程池字段置爲pThreadpool指向的線程池。當不調用此函數時,PTP_CALLBACK_ENVIRON結構的線程池字段一直爲NULL,當用這個回調環境來添加工作項時,工作項會被添加到默認的線程池中。

 

    當我們不需要回調環境時可以調用DestroyThreadEnvironment來將其銷燬:

 

  1. VOID DestroyThreadEnvironment(PTP_CALLBACK_ENVRION pcbe);  
VOID DestroyThreadEnvironment(PTP_CALLBACK_ENVRION pcbe);


 

    線程池同樣提供了將自己銷燬的機制。默認的線程池不會被銷燬,它與進程的生命期一樣長。進程結束後,Windows將負責所有的清理操作。

 

    要想對我們自定義的線程池執行清理操作,首先需要創建一個清理組:

 

 

  1. PTP_CLEANUP_GROUP CreateThreadpoolCleanuGroup();  
PTP_CLEANUP_GROUP CreateThreadpoolCleanuGroup();


 

    創建清理組後還需要將清理組和線程池相關聯:

 

 

  1. VOID SetThreadpoolCallbackCleanupGroup(  
  2.   
  3. PTP_CALLBACK_ENVIRON pcbe,  
  4.   
  5. PTP_CLEANUP_GROUP ptpcg,  
  6.   
  7. PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng);  
VOID SetThreadpoolCallbackCleanupGroup(

PTP_CALLBACK_ENVIRON pcbe,

PTP_CLEANUP_GROUP ptpcg,

PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng);


 

此函數會設置回調環境的清理組字段爲ptpcg指定的清理組。

 

pfng指向一個回調函數。當清理組被取消時回調函數將會被調用。該回調函數必須滿足一下原型:

 

 

  1. VOID CALLBACK CleanupGroupCancelback(  
  2.   
  3.      PVOID pvObjectContext,  
  4.   
  5.      PVOID pvCleanupContext);  
VOID CALLBACK CleanupGroupCancelback(

     PVOID pvObjectContext,

     PVOID pvCleanupContext);


 

當我們調用CreateThreadpoolWorkCreateThreadpoolTimerCreateThreadpoolWaitCreateThreadpoolIo時,如果最後的指向PTP_CALLBACK_ENVIRON結構的指針不爲NULL,那麼所創建的項會被添加到對應的回調函數清理組中。它表示線程池中又添加了一項,需要清理。當我們調用CloseThreadpoolWorkCloseThreadpoolTimerCloseThreadpoolWait或是CloseThreadIo時,等於是隱式的將對應項從清理組中清理。

 

當我們想清理線程池時可以調用:

 

 

  1. VOID CloseThreadpoolCleanupGroupMemebers(  
  2.   
  3.     PTP_CLEANUPGROUP ptpcg,  
  4.   
  5.    BOOL bCancelPendingCallbacks,  
  6.   
  7.    PVOID pvCleanupContext);  
VOID CloseThreadpoolCleanupGroupMemebers(

    PTP_CLEANUPGROUP ptpcg,

   BOOL bCancelPendingCallbacks,

   PVOID pvCleanupContext);


 

如果傳入falsebCancelPendignCallbacks,調用此函數的線程會一直等待直到線程池工作組中所有剩餘的項當已經處理完畢爲止。當傳入truebCancelPendingCallbacks時,所有已提交但未處理的工作項將直接被取消。對於每一個待處理的項,pfng指向的回調函數將會被調用。

 

當所有的工作項都被取消或被處理後,可以調用CloseThreadpoolCleanupGroup來釋放清理組所佔的資源:

 

  1. <SPAN style="FONT-SIZE: 18px">VOID WINAPI CloseThreadpoolCleanupGroup(  
  2.   
  3.      PTP_CLEANUP_GROUP ptpcg);  
  4. </SPAN>  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章