線程池

線程池（thread pool）：一種線程使用模式。線程過多會帶來調度開銷，進而影響緩存局部性和整體性能。而線程池維護著多個線程，等待著監督管理者分配可併發執行的任務。這避免了在處理短時間任務時創建與銷燬線程的代價。線程池不僅能夠保證內核的充分利用，還能防止過分調度。可用線程數量應該取決於可用的併發處理器、處理器內核、內存、網絡sockets等的數量。 例如，對於計算密集型任務，線程數上限一般取CPU邏輯核心數+2，線程數過多會導致額外的線程切換開銷。
任務調度以執行線程的常見方法是使用同步隊列，稱作任務隊列。池中的線程等待隊列中的任務，並把執行完的任務放入完成隊列中。
線程池模式一般分為兩種：HS/HA半同步/半異步模式、L/F領導者與跟隨者模式。 
*半同步/半異步模式又稱為生產者消費者模式，是比較常見的實現方式，比較簡單。分為同步層、隊列層、異步層三層。同步層的主線程處理工作任務並存入工作隊列，工作線程從工作隊列取出任務進行處理，如果工作隊列為空，則取不到任務的工作線程進入掛起狀態。由於線程間有數據通信，因此不適於大數據量交換的場合。
*領導者跟隨者模式，在線程池中的線程可處在3種狀態之一：領導者leader、追隨者follower或工作者processor。任何時刻線程池只有一個領導者線程。事件到達時，領導者線程負責消息分離，並從處於追隨者線程中選出一個來當繼任領導者，然後將自身設置為工作者狀態去處置該事件。處理完畢後工作者線程將自身的狀態置為追隨者。這一模式實現複雜，但避免了線程間交換任務數據，提高了CPU cache相似性。在ACE(Adaptive Communication Environment)中，提供了領導者跟隨者模式實現。 
線程池的伸縮性對性能有較大的影響。- 創建太多線程，將會浪費一定的資源，有些線程未被充分使用。- 銷毀太多執行緒，將導致之後浪費時間再次創建它們。- 創建線程太慢，將會導致長時間的等待，性能變差。- 銷毀執行緒太慢，導致其它執行緒資源飢餓。

**Windows API的線程池函數**
Windows操作系統的API提供了一套線程池的實現接口。可以方便地創建、使用線程池。Windows線程池API被設計為一組協同對象， 其中有些對象表示工作單位、計時器、異步I/O 等等。使用下述用戶模式的對象來管理線程池及相關的數據：
*線程池對象（pool object）包含了一組工作線程（worker threads）。每個進程可以創建多個線程池對象，也可以直接使用進程缺省的線程池。
*回調環境(Threadpool Callback Environment)線程池可以關聯多個回調環境，回調環境可單獨指定在線程池內的調度優先級。
*清理群（clean-up group）對象關聯於一個回調環境。用於追溯(track)線程緩衝池回調。每次創建線程池的IO、work、timer、wait等對象（調用CreateThreadpool*等函數），會在cleanup group中增加一個成員。關閉線程池的IO、work、timer、wait等對象（調用CloseThreadpool*等函數），會在cleanup group中刪除相應的成員。 
*線程池工作對象（work object） 由一個函數指針和一個被稱為上下文的 void 指針組成，線程池每次在執行時將 void 指針送入函數。可異步多次投寄給線程池去執行它的回調函數。實際上就是以異步方式調用回調函數。通過SubmitThreadpoolWork投寄工作對象到任務隊列。如果不創建新的工作對象，則不能更改函數和上下文。
*線程池定時器對象（timer object）在定時器到期時投寄給線程池去執行它的回調函數。 創建(CreateThreadpoolTimer)並設定(SetThreadpoolTimer)定時器對象。當定時器對象到期時，工作線程會執行定時器對象的回調函數。
*線程池等待對象（wait object）：在可等待句柄（waitable handle，可使用任何內核同步對象的句柄）被觸發（signaled）時或超時到期時，它的回調函數被投寄給線程池去執行。創建(CreateThreadpoolWait)並設定(SetThreadpoolWait)等待對象。線程池內部的一個線程調用WaitForMultipleObjects並傳入由SetThreadpoolWait函數註冊的句柄，不斷地組成一個句柄組，同時將Wait*函數的bWaitAll設為FALSE，這樣當任何一個句柄被觸發，線程池就會被喚醒。因WaitForMultipleObjects不允許將同一個句柄傳入多次，因此必須確保不會用SetThreadpoolWait來多次註冊同一個句柄，但可以調用DuplicationHandle複製句柄並傳給Set*函數。因WaitForMultipleObjects一次最多隻能等待64個內核對象，因此線程池實際上為每64個內核對象分配一個線程來等待，所以效率比較高。如果要等待超過64個以上的內核對象，系統會每64個內核對象，就開闢一個線程來等待這些內核對象。當線程池中一個線程調用了傳入的回調函數，對應的等待項將進入“不活躍”狀態；這意味著如果在同一個內核對象被觸發後如果想再次調用這個回調函數，需要調用SetThreadpoolWait再次註冊；如果傳入的hObject為NULL，將把pWaitItem這個等待項從線程中移除。 
*線程池I/O對象把文件句柄關聯到線程池的I/O完成端口（completion port）。當異步I/O操作完成，一個工作線程取得操作的狀態並調用I/O對象的回調函數。  

相關的API函數：- CreateThreadpool 創建一個線程池數據結構- CloseThreadpool  關閉一個線程池- SetThreadpoolThreadMaximum 設置一個線程池的線程數量上限。默認線程池的最小數量為1，最大數量為500- SetThreadpoolThreadMinimum 設置一個線程池的線程數量下限- InitializeThreadpoolEnvironment 初始化一個回調環境。作用是回調環境結構體中的將Version設為1，其餘為0。- DestroyThreadpoolEnvironment 刪除回調環境- SetThreadpoolCallbackPool 把指定的線程緩衝池關聯到指定的線程池回調環境。創建線程池的IO、work、timer、wait等對象會關聯到一個回調環境上。如果使用系統默認的回調環境，需要自行負責回調函數的DLL保持裝入狀態，自行管理線程池內的各種對象的生存期。線程池可以關聯多個回調環境，回調環境可單獨指定在線程池內的調度優先級。 - SetThreadpoolCallbackLibrary  指示在回調函數未執行完的時候其所在的DLL庫不能卸載- SetThreadpoolCallbackPriority 設定回調環境在所屬線程池內的優先級- SetThreadpoolCallbackRunsLong 指示當前回調函數將運行較長時間。這有助於線程池判斷是否創建新線程 - CreateThreadpoolCleanupGroup 創建一個cleanup group數據結構，關聯到一個回調環境上- SetThreadpoolCallbackCleanupGroup 把指定的cleanup group關聯到指定的回調環境。第三個參數設置CleanupGroupCancelCallback回調函數。當調用清理組的CloseThreadpoolCleanupGroupmembers函數，併為bCancelPendingCallbacks傳入TRUE來清除清理組時，如果此時尚有未被處理的工作項時，則這個回調函數被調用，這個回掉函數的第一個參數pvObjectContext是通過CreateThreadpool*函數傳入的pvContext，第二個參數pvCleanupContext是由CloseThreadpoolCleanupGroupMembers的pvCleanupContext參數傳入的。- CloseThreadpoolCleanupGroup 關閉一個cleanup group數據結構。cleanup group不能包含任何成員。- CloseThreadpoolCleanupGroupMembers 把指定cleanup group的所有work、timer、wait等對象釋放掉。對於正在執行的回調函數，會等待其執行完畢。參數fCancelPendingCallbacks確定是否尚未開始執行的任務執行。- CreateThreadpoolWork 創建一個線程池工作對象的數據結構- CloseThreadpoolWork 函數通知線程池該對象可釋放。- SubmitThreadpoolWork 投寄（post）一個工作對象到線程池中。一個工作線程將會調用執行這個工作對象的回調函數。 - TrySubmitThreadpoolCallback 請求線程池一個工作線程調用指定的回調函數- CreateThreadpoolTimer 創建一個線程池定時器對象的數據結構- SetThreadpoolTimer 設定一個線程定時器對象的參數。當定時器到期時，這個定時器對象的回調函數被放入任務隊列等待被一個工作線程執行。第三個參數設定週期性到時，不論之前放入任務隊列的回調函數是否執行完畢，總是把回調函數再一次放入任務隊列等待被執行。如果不希望一個定時器對象的回調函數的多個實例併發重疊執行，可以不設定週期性到時參數，而在回調函數末尾調用SetThreadpoolTimer重置這個定時器對象，起碼可以簡化調試。如果調用SetThreadpoolTimer時當前定時器對象還沒有到期，則會修改該定時器對象的到期時間等屬性。第二個參數pftDueTime表示首次到時時間，如果為NULL，則取消還未到時的定時器的下次到期，不再向任務隊列放入定時器對象的回調函數，但已經在任務隊列中（正在被執行）的回調函數仍會被執行。第四個參數是到時的容限上限，涉及定時器匯聚，如計時器A會在5-7微秒內被觸發，計時器B會在6-8微秒內。因時間有重疊，所以線程池只會喚醒一個線程來處理這兩個計時器，以減少用兩個線程調用時產生的額外的線程上下文切換的開銷。- IsThreadpoolTimerSet 判斷給定的定時器對象當前是否被設置- CreateThreadpoolWait 創建一個線程池等待（wait）對象的數據結構- SetThreadpoolWait 設定一個線程等待對象與一個可等待對象(waitable object)的句柄綁定並設定超時時間。當可等待對象的句柄被通知(signaled)或指定的超時到期，這個等待對象的回調函數會被入列（queue）進入任務隊列中並等待被一個工作線程執行。如果可等待對象（即函數的第二個參數）的句柄被設置為NULL，（由於可等待對象還沒有signaled所以）線程池等待對象的回調函數還未放入任務隊列的都被取消，但已在任務隊列的回調函數仍會被工作線程執行。注意不要多次使用SetThreadpoolWait來等待同一個hObject。第三個參數pftTimeout指出線程池願意花的最長時間來等待內核對象觸發，0是立即返回，負值為相對時間，正值為絕對時間，NULL表示無限等待（線程池內部調用了WaitForMultipleObjects）。- CloseThreadpoolWait 關閉一個線程等待對象，然後才能關閉可等待對象(waitable object)的句柄。- WaitForThreadpoolWaitCallbacks 等待指定的線程池等待對象執行完成或者取消還沒開始執行的線程池等待對象。- WaitForThreadpoolIoCallbacks 等待指定的線程池IO對象執行完成或者取消還沒開始執行的線程池IO對象。該函數須在另一個線程使用，而不能在回調函數內部使用，因為這會造成死鎖。如果bCancelPendingCallbacks為TRUE，那麼當請求完成的時候，回調函數不會被調用（如果尚未被調用）。- WaitForThreadpoolTimerCallbacks 等待指定的線程池定時器對象執行完成或者取消還沒開始執行的線程池定時器對象。- WaitForThreadpoolWorkCallbacks 等待指定的線程池工作對象執行完成或者取消還沒開始執行的線程池工作對象。- CreateThreadpoolIo 創建一個IO完成對象，並綁定一個異步IO的句柄。 - CloseThreadpoolIo 釋放IO完成對象。應等待所有IO操作完成，關閉IO文件句柄後再調用本函數。解除IO對象（工作項）與線程池的關聯。 - StartThreadpoolIo 初始化異步IO操作前應先調用本函數，否則線程池會忽略IO操作的完成並內存洩露。- CancelThreadpoolIo 在IO失敗或者IO直接同步完成時，取消對線程池的通知（notification） - CallbackMayRunLong 回調函數通知系統需要執行較長時間。只能在調用線程的回調函數里使用。返回TRUE時，說明線程池還有其他線程可供使用。FALSE則相反。- DisassociateCurrentThreadFromCallback 表示與回調函數關聯的工作項在“邏輯上”完成了，執行當前回調函數的線程與初始化當前回調函數的對象去關聯。這使得所有等待當前回調函數完成的線程被釋放。任何由於調用WaitForThreadpool*Callbacks（如WaitForThreadpoolIoCallbacks）而被阻塞的線程能早一些返回，而不必等到線程從回調函數中結束時才返回。- 下述函數與回調函數實體指針PTP_CALLBACK_INSTANCE有關，用於在回調函數內設置當該函數結束時的動作。注意只能有一個動作在回調函數結束後被執行，不能多次調用下述函數，否則最後一次調用的函數會覆蓋之前調用的下述函數。當線程調用回調函數時，Windows會自動傳一個pInstance參數（類型PTP_CALLBACK_INSTANCE)給回調函數，然後回調函數將這個參數又傳給如下的函數，執行一些相應的終止操作：
** FreeLibraryWhenCallbackReturns 當回調函數返回的時候，線程池會自動調用FreeLibrary，並在參數中傳入指定的HMOUDLE。如果回調函數是從DLL中載入的，這個函數尤為重要，因為當線程執行完畢後，回調函數不能自己調用FreeLibrary，否則回調函數代碼將從進程中清除，這樣當FreeLibrary試圖返回到回調函數時，會引發訪問違規
** LeaveCriticalSectionWhenCallbackReturns 當回調函數返回時，線程池會自動調用LeavCriticalSection，並在參數中傳入指定的CRITCAL_SECTION結構體。
** ReleaseMutexWhenCallbackReturns 當回調函數返回時，線程池會自動調用ReleaseMutex，並在參數中傳入指定的HANDLE
** ReleaseSemaphoreWhenCallbackReturns 當回調函數返回的時候，線程池會自動調用ReleaseSemaphore，並在參數中傳入指定的HANDLE
** SetEventWhenCallbackReturns 當回調函數返回的時候，線程池會自動調用SetEvent，並在參數中傳入指定的HANDLE

**示例**
示例程序1如下：
#include 
#include 
#include 
//
// Thread pool wait callback function template
//
VOID CALLBACK MyWaitCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult)
{
	// Instance, Parameter, Wait, and WaitResult not used in this example.
	UNREFERENCED_PARAMETER(Instance); 	UNREFERENCED_PARAMETER(Parameter);	UNREFERENCED_PARAMETER(Wait); 	UNREFERENCED_PARAMETER(WaitResult);
	// Do something when the wait is over.
	_tprintf(_T("MyWaitCallback: wait is over.\n"));
}
//
// Thread pool timer callback function template
//
VOID CALLBACK MyTimerCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_TIMER Timer)
{
	// Instance, Parameter, and Timer not used in this example.
	UNREFERENCED_PARAMETER(Instance); UNREFERENCED_PARAMETER(Parameter); UNREFERENCED_PARAMETER(Timer);
	// Do something when the timer fires.
	_tprintf(_T("MyTimerCallback: timer has fired.\n"));
}
//
// This is the thread pool work callback function.
//
VOID CALLBACK MyWorkCallback( PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WORK Work)
{
	// Instance, Parameter, and Work not used in this example.
	UNREFERENCED_PARAMETER(Instance);UNREFERENCED_PARAMETER(Parameter);UNREFERENCED_PARAMETER(Work);
	// Do something when the work callback is invoked.
	_tprintf(_T("MyWorkCallback: Task performed.\n"));
}

int main(void)
{
	PTP_WAIT Wait = NULL;
	PTP_WAIT_CALLBACK waitcallback = MyWaitCallback;
	HANDLE hEvent = NULL;
	UINT i = 0;
	UINT rollback = 0;
	// Create an auto-reset event and initialized as nonsignaled.
	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (NULL == hEvent) {
		// Error Handling
		return 0;
	}
	rollback = 1; // CreateEvent succeeded
	Wait = CreateThreadpoolWait(waitcallback,
		NULL,     // 回調函數的輸入參數
		NULL);    // 使用缺省的回調環境
	if (NULL == Wait) {
		_tprintf(_T("CreateThreadpoolWait failed. LastError: %u\n"),GetLastError());
		goto new_wait_cleanup;
	}
	rollback = 2; // CreateThreadpoolWait succeeded
				  // must re-register the event with the wait object before signaling it each time to trigger the wait callback
				  // each time before signaling the event to trigger the wait callback.
	for (i = 0; i 
關於IO線程池的一個示例：
#include 
#include 
#include 
#include  
#include 
#include 

void PressEnterToContinue()
{
	std::cout ::max)( ), '\n');
}
//////////////////////////////////////////////////////////////////////////
#define  QMLX_ALLOC(sz)    HeapAlloc(GetProcessHeap(),0,sz)   
#define  QMLX_CALLOC(sz)   HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sz)
#define  QMLX_SAFEFREE(p)  if(NULL != p){HeapFree(GetProcessHeap(),0,p);p=NULL;}
#define  QMLX_ASSERT(s)    if(!(s)){DebugBreak();}
#define  QMLX_BEGINTHREAD(Fun,Param)  CreateThread(NULL,0,\
(LPTHREAD_START_ROUTINE)Fun,Param,0,NULL);
//////////////////////////////////////////////////////////////////////////
#define MAXWRITEPERTHREAD 2  //每個線程最大寫入次數
#define MAXWRITETHREAD    2   //寫入線程的數量
#define OP_READ    0x01  //讀操作
#define OP_WRITE   0x02  //寫操作
//#pragma pack(show)
//單IO數據
typedef struct __declspec(align(16)) _tagPerIoData {
	OVERLAPPED  m_ol;
	HANDLE      m_hFile;   //操作的文件句柄
	DWORD       m_dwOp;    //操作類型，OP_READ或OP_WRITE
	LPVOID      m_pData;   //操作的數據
	UINT        m_nLen;    //操作的數據長度
	DWORD       m_dwWrite; //寫入的字節數 
	DWORD       m_dwTimestamp; //起始操作的時間戳
}PER_IO_DATA, *PPER_IO_DATA;
//IOCP線程池回調函數，實際就是完成通知的響應函數
VOID CALLBACK IOCPCallback(PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PVOID pOverlapped,
	ULONG IoResult, ULONG_PTR NumberOfBytesTransferred, PTP_IO pio);
//寫文件的線程
DWORD WINAPI WriteThread(LPVOID lpParam);
//當前操作的文件對象的指針
LARGE_INTEGER  g_liFilePointer = { 0 };
//IOCP線程池
PTP_IO  g_pThreadpoolIo = NULL;
//////////////////////////////////////////////////////////////////////////
//獲取可模塊的路徑名（路徑後含‘\’)
VOID  GetAppPath(LPTSTR pszBuffer) {
	DWORD dwLen = 0;
	if (0 == (dwLen = GetModuleFileName(NULL, pszBuffer, MAX_PATH)))
		return;
	for (DWORD i = dwLen; i > 0; i--) {
		if ('\\' == pszBufferi) {
			pszBufferi + 1 = '\0';
			break;
		}
	}
}
int _tmain() {
	 
	_tsetlocale(LC_ALL, _T("chs"));
	TCHAR pFileNameMAX_PATH = {};
	GetAppPath(pFileName);
	StringCchCat(pFileName, MAX_PATH, _T("NewIOCPFile.txt"));
	HANDLE ahWThreadMAXWRITETHREAD = {};
	DWORD dwWrited = 0;
	//創建文件
	HANDLE hTxtFile = CreateFile(pFileName, GENERIC_WRITE, 0, NULL,
		CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
	if (INVALID_HANDLE_VALUE == hTxtFile) {
		_tprintf(_T("CreateFile(%s)失敗，錯誤碼：%u\n"), GetLastError());
		_tsystem(_T("PAUSE"));
		return 0;
	}
	//初始化線程池回調環境
	TP_CALLBACK_ENVIRON poolEnv = {};
	InitializeThreadpoolEnvironment(&poolEnv);
	//創建IOCP線程池
	g_pThreadpoolIo = CreateThreadpoolIo(hTxtFile, (PTP_WIN32_IO_CALLBACK)IOCPCallback, hTxtFile, &poolEnv);
	//啟動IOCP線程池
	StartThreadpoolIo(g_pThreadpoolIo);
	//寫入UNICODE文件的前綴碼，以便正確打開
	PER_IO_DATA* pIo = (PPER_IO_DATA)QMLX_CALLOC(sizeof(PER_IO_DATA));
	QMLX_ASSERT(pIo != NULL);
	pIo->m_dwOp = OP_WRITE;
	pIo->m_hFile = hTxtFile;
	pIo->m_pData = QMLX_CALLOC(sizeof(WORD));
	QMLX_ASSERT(pIo->m_pData != NULL);
	*((WORD*)pIo->m_pData) = MAKEWORD(0xFF, 0xFE);
	pIo->m_nLen = sizeof(WORD);
	//偏移文件指針
	pIo->m_ol.Offset = g_liFilePointer.LowPart;
	pIo->m_ol.OffsetHigh = g_liFilePointer.HighPart;
	g_liFilePointer.QuadPart += pIo->m_nLen;
	pIo->m_dwTimestamp = GetTickCount(); //記錄時間戳
	WriteFile(hTxtFile, pIo->m_pData, pIo->m_nLen,
		&pIo->m_dwWrite, (LPOVERLAPPED)&pIo->m_ol);
	//等待IOCP線程池完成操作
	WaitForThreadpoolIoCallbacks(g_pThreadpoolIo, FALSE);
	//啟動寫入線程進行日誌寫入操作
	for (int i = 0; i m_dwOp)
	{
	case OP_WRITE://寫操作結束
	{//寫入操作結束
		_tprintf(_T("線程0x%x得到IO完成通知,完成操作(%s),緩衝(0x%08x)長度(%ubytes),寫入時間戳(%u)當前時間戳(%u)時差(%u)\n"),
			GetCurrentThreadId(), OP_WRITE == pIo->m_dwOp ? _T("Write") : _T("Read"),
			pIo->m_pData, pIo->m_nLen, pIo->m_dwTimestamp, dwCurTimestamp, dwCurTimestamp - pIo->m_dwTimestamp);
		QMLX_SAFEFREE(pIo->m_pData);
		QMLX_SAFEFREE(pIo);
	}
	break;
	case OP_READ: //讀操作結束
		break;
	default:
		break;
	}
}
//寫文件的線程
#define MAX_LOGLEN 256
DWORD WINAPI WriteThread(LPVOID lpParam)
{
	TCHAR pTxtContextMAX_LOGLEN = {};
	PPER_IO_DATA pIo = NULL;
	size_t szLen = 0;
	LPTSTR pWriteText = NULL;
	StringCchPrintf(pTxtContext, MAX_LOGLEN, _T("這是一條模擬的日誌記錄,由線程0x%x寫入\r\n"),
		GetCurrentThreadId());
	StringCchLength(pTxtContext, MAX_LOGLEN, &szLen);
	szLen += 1;
	int i = 0;
	for (; i m_dwOp = OP_WRITE;
		pIo->m_hFile = (HANDLE)lpParam;
		pIo->m_pData = pWriteText;
		pIo->m_nLen = (szLen - 1) * sizeof(TCHAR);
		//這裡使用原子操作同步文件指針，寫入不會相互覆蓋
		//這個地方體現了lock-free算法的精髓,使用了基本的CAS操作控制文件指針
		//比傳統的使用關鍵代碼段並等待的方法,這裡用的方法要輕巧的多,付出的代價也小
		*((LONGLONG*)&pIo->m_ol.Pointer) = InterlockedCompareExchange64(&g_liFilePointer.QuadPart,
			g_liFilePointer.QuadPart + pIo->m_nLen, g_liFilePointer.QuadPart);
		pIo->m_dwTimestamp = GetTickCount(); //記錄時間戳
		StartThreadpoolIo(g_pThreadpoolIo);
		//寫入
		WriteFile((HANDLE)lpParam, pIo->m_pData, pIo->m_nLen,
			&pIo->m_dwWrite, (LPOVERLAPPED)&pIo->m_ol);
		if (ERROR_IO_PENDING != GetLastError()) {
			CancelThreadpoolIo(g_pThreadpoolIo);
		}
	} 
	return i;
}

**.NET Framework的線程池實現**
命名空間System.Threading中的類ThreadPool提供一個線程池，該線程池可用於執行任務、發送工作項、處理異步 I/O、代表其他線程等待以及處理計時器。

**參看**
併發
*並行
*對象池模式

**參考文獻==