《Windows核心编程》读书笔记十一 Windows线程池

第11章  Windows线程池


本章内容

11.1 情形1: 以异步方式调用函数

11.2 情形2:每隔一段时间调用一个函数

11.3 情形3:在内核对象触发时调用一个函数

11.4 情形4:在异步I/O请求完成时调用一个函数

11.5 回调函数的终止操作


Windows系统的线程池函数允许我们做这些事情:

1)以异步方式来调用一个函数

2)每隔一段时间调用一个函数

3)当内核对象触发的时候调用一个函数

4)当异步I/O请求完成时调用一个函数



默认情况当一个进程创建的时候,它并没有与任何线程池有关的开销。若调用了新的线程池函数,系统就会为进程创建相应的内核资源,其中一些资源在进程终止之前都将一直存在。


11.1 情形1: 以异步方式调用函数

为了使用线程池来以异步方式执行一个函数,需要定义一个具有以下原型的函数

VOID NTAPI SimpleCallback(
	PTP_CALLBACK_INSTANCE pInstance,
	PVOID pvContext);
然后为了让线程池中的一个线程执行该函数,需要向线程池提交一个请求。为了达到这个目的,我们只需要调用下面函数。

WINBASEAPI
_Must_inspect_result_
BOOL
WINAPI
TrySubmitThreadpoolCallback(
    _In_ PTP_SIMPLE_CALLBACK pfns,
    _Inout_opt_ PVOID pv,
    _In_opt_ PTP_CALLBACK_ENVIRON pcbe
    );
该函数(通过调用PostQueuedCompletionStatus)来将一个工作项(work item)添加到线程池的队列中,若调用成功,则返回TRUE,若失败则返回FALSE

pfns 前面申明的那个原型函数

pv 对应原型函数的pvContext参数,会传递给原型函数。

pcbe 传递NULL

注意:不需要调用CreateThread来创建线程池,系统会自动为我们创建线程池,并自动分配线程池中的线程来调用我们的回调函数。

此外当线程处理完一个请求以后,不会立即销毁,而是回到线程池,准备好处理队列中的其他工作项。

线程池会不断重复使用其中的线程,而不会频繁的创建和销毁。

当然某些情况下创建新的线程,某些情况下会销毁线程(线程池算法)


11.1.1 显示地控制工作项

某些情况下TrySubmitThreadpoolCallback可能会失败。

当多个操作需要互相协调的时候(比如一个计时器需要依靠一个工作项来取消另一个操作的时候)这是不能接受的。

每次调用TrySubmitThreadpoolCallback的时候,系统内部会以我们的名义分配一个工作项。如果打算提交大量工作项,那么处于性能和内存考虑,创建一个工作项一次,然后分多次提交会更好。调用下面函数来创建一个工作项:

WINBASEAPI
_Must_inspect_result_
PTP_WORK
WINAPI
CreateThreadpoolWork(
    _In_ PTP_WORK_CALLBACK pfnwk,
    _Inout_opt_ PVOID pv,
    _In_opt_ PTP_CALLBACK_ENVIRON pcbe
    );
这个函数会在用户模式中创建一个结构来保存它的三个参数,并返回指向该结构的指针。

pfnwk 是一个函数指针,被线程池中的函数调用。

pv 传给回调函数的参数

pcbe 设置为NULL


PTP_WORK_CALLBACK原型

VOID CALLBACK WorkCallback(
	PTP_CALLBACK_INSTANCE hInstance,
	PVOID Context,
	PTP_WORK work);
当我们想要想线程池提交一个请求的时候,可以调用SubmitThreadpoolWork函数

WINBASEAPI
VOID
WINAPI
SubmitThreadpoolWork(
    _Inout_ PTP_WORK pwk
    );
现在已经可以假定将工作项成功添加到队列了。

如果有另一个线程,该线程想要取消已经提交的工作项,或者该线程由于要等待工作项处理完毕而需要将自己挂起,那么可以调用下面函数:


WINBASEAPI
VOID
WINAPI
WaitForThreadpoolWorkCallbacks(
    _Inout_ PTP_WORK pwk,
    _In_ BOOL fCancelPendingCallbacks
    );

pWork指向一个工作项,由CreateThreadpoolWork创建并由SubmitThreadpoolWork来提交。

如果该工作项未被提交,那么该函数将立即返回而不执行任何操作。


如果传递TRUE 给fCancelPendingCallbacks函数,那么WaitForThreadpoolWorkCallbacks会试图取消之前提交的那个工作项。

如果该工作项已经被线程池中的线程进行处理,那么该工作并不会被打断。WaitForThreadpoolWorkCallbacks会等待其处理完毕再返回。

如果该工作项尚未被任何线程处理,那么他会被取消并理解返回。(当完成端口取出该工作项的时候,线程池知道无需调用函数,这样该工作项根本不会被执行)


如果fCancelPendingCallbacks传递FALSE,那么该函数会将线程挂起知道指定的工作项处理完成。



不再需要一个工作项的时候,应该调用CloseThreadpoolWork并传入其指针将其释放。

WINBASEAPI
VOID
WINAPI
CloseThreadpoolWork(
    _Inout_ PTP_WORK pwk
    );

11.1.2 Batch示例程序

Batch展示了如何使用线程池的工作项来实现对多个操作进行批处理,每个操作会通知用户界面该操作的状态。并以执行该操作的线程的表示符为前缀。



源代码

/******************************************************************************
Module:  Batch.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include <windowsx.h>

// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <strsafe.h>

#include "resource.h"

//

// Global variables
HWND		g_hDlg = NULL;
PTP_WORK	g_pWorkItem = NULL;
volatile LONG	g_nCurrentTask = 0;

// Global definitions
#define WM_APP_COMPLETED	(WM_APP+123)

//

void AddMessage(LPCTSTR szMsg) {

	HWND hListBox = GetDlgItem(g_hDlg, IDC_LB_STATUS);
	ListBox_SetCurSel(hListBox, ListBox_AddString(hListBox, szMsg));
}

//

void NTAPI TaskHandler(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) {

	LONG currentTask = InterlockedIncrement(&g_nCurrentTask);

	TCHAR szMsg[MAX_PATH];
	StringCchPrintf(
		szMsg, _countof(szMsg),
		TEXT("[%u] Task #%u is starting."), GetCurrentThreadId(), currentTask);
	AddMessage(szMsg);

	// Simulate a lot of work
	Sleep(currentTask * 1000);

	StringCchPrintf(
		szMsg, _countof(szMsg),
		TEXT("[%u] Task #%u is done."), GetCurrentThreadId(), currentTask);
	AddMessage(szMsg);

	if (InterlockedDecrement(&g_nCurrentTask) == 0) {

		// Notify the UI thread for completion.
		PostMessage(g_hDlg, WM_APP_COMPLETED, 0, (LPARAM)currentTask);
	}
}


//

void OnStartBatch() {

	// Disable Start button
	Button_Enable(GetDlgItem(g_hDlg, IDC_BTN_START_BATCH), FALSE);

	AddMessage(TEXT("----Start a new batch----"));

	// Submit 4 tasks by using the same work item
	SubmitThreadpoolWork(g_pWorkItem);
	SubmitThreadpoolWork(g_pWorkItem);
	SubmitThreadpoolWork(g_pWorkItem);
	SubmitThreadpoolWork(g_pWorkItem);

	AddMessage(TEXT("4 tasks are submitted."));
}


//

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

	switch (id) {
	case IDOK:
	case IDCANCEL:
		EndDialog(hWnd, id);
		break;

	case IDC_BTN_START_BATCH:
		OnStartBatch();
		break;
	}
}


BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {

	chSETDLGICONS(hWnd, IDI_MY10BATCH);
	// Keep track of main dialog window for error messages
	g_hDlg = hWnd;

	return TRUE;
}

//

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

	switch (uMsg) {
		chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
		chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
		case WM_APP_COMPLETED: 
		{
			TCHAR szMsg[MAX_PATH + 1];
			StringCchPrintf(
				szMsg, _countof(szMsg),
				TEXT("____Task #%u was the last task of the batch____"), lParam);

			// Don't forget to enable the button
			Button_Enable(GetDlgItem(hWnd, IDC_BTN_START_BATCH), TRUE);
		}
		break;
	}

	return FALSE;
}


//

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR pCmdLine, int) {

	// Create the work item that will be used by all tasks
	g_pWorkItem = CreateThreadpoolWork(TaskHandler, NULL, NULL);
	if (g_pWorkItem == NULL) {
		MessageBox(NULL, TEXT("Impossible to create the work item for tasks."),
			TEXT(""), MB_ICONSTOP);
	}

	DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, Dlg_Proc,
		_ttoi(pCmdLine));

	// Dont't forget to delete the work item
	CloseThreadpoolWork(g_pWorkItem);

	return 0;
}


11.2 情形2:每隔一段时间调用一个函数

Windows提供了可等待计时器内核对象,它使我们非常容易就能够得到一个基于时间的通知。

线程池封装了可等待计时器可以供我们直接使用。

为了将一个工作项安排在某个时间执行,定义一个回调函数

VOID CALLBACK TimeoutCallback(
	PTP_CALLBACK_INSTANCE pInstance,
	PVOID pvContext,
	PTP_TIMER pTimer);

通过下面函数来通知线程池应该在何时调用我们的函数:

WINBASEAPI
_Must_inspect_result_
PTP_TIMER
WINAPI
CreateThreadpoolTimer(
    _In_ PTP_TIMER_CALLBACK pfnti,
    _Inout_opt_ PVOID pv,
    _In_opt_ PTP_CALLBACK_ENVIRON pcbe
    );
pfnti 是前面定义的回调函数

pv 是传递给回调函数的参数

pcbe设置为NULL


成功会返回一个计时器对象。

向线程池注册计时器采用以下函数

WINBASEAPI
VOID
WINAPI
SetThreadpoolTimer(
    _Inout_ PTP_TIMER pti,
    _In_opt_ PFILETIME pftDueTime,
    _In_ DWORD msPeriod,
    _In_opt_ DWORD msWindowLength
    );
pTimer 表示由CreateThreadpoolTimer创建返回的计时器对象

pftDueTime 第一次调用回调函数应该是什么时候(可以传一个负值来指定一个相对时间 但-1表示立即开始)

如果使用绝对时间应该以100纳秒为单位。从1600年1月1日开始计算。(参考第九章可等待定时器)


如果只想让计时器触发一次,可以传msPeriod 为0

如果要让线程池定期调用,那么传递一个非0 值,表明周期(单位是微妙)

msWindowLength 用来给回调函数的执行时间增加一些随机性,这使得回调函数会被之前设定的触发时间加上msWindowLength设定的时间触发。

(防止多个计时器冲突)

另外线程池调度算法会优化触发值相近的回调函数,避免大量的context切换浪费系统资源。


另外在设置了计时器以后可以再次调用SetThreadpoolTimer并传入之前的计时器对象对其进行修改。

也可以传入pftDueTime = 0 表明停止计时器调用TimerCallback函数


可以调用IsThreadpoolTimerSet来确定某个计时器是否已经被设置(也就是说,它的pftDueTime值不为NULL)

WINBASEAPI
BOOL
WINAPI
IsThreadpoolTimerSet(
    _Inout_ PTP_TIMER pti
    );
最后可以调用WaitForThreadpoolTimerCallbacks来让线程等待一个计时器完成,还可以调用

CloseThreadpoolTimer来释放计时器的内存。


Timed Message Box示例程序





/******************************************************************************
Module:  TimedMsgBox.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include <tchar.h>
#include <StrSafe.h>

//

// The caption of our message box
TCHAR g_szCaption[100];

// How many second we'll display the message box
int g_nSecLeft = 0;

// This is STATIC window control ID for a message box
#define ID_MSGBOX_STATIC_TEXT	0x0000ffff

//

VOID CALLBACK MsgBoxTimeoutCallback(
	PTP_CALLBACK_INSTANCE	pInstance,
	PVOID					pvContext,
	PTP_TIMER				pTimer
	) 
{

	// NOTE: Due to a thread race condition, it is possible (but very unlikely)
	// that the message box will not be created when we get here.
	HWND hwnd = FindWindow(NULL, g_szCaption);

	if (hwnd != NULL) {
		if (g_nSecLeft == 1) {
			// The time is up; force the message box to exit.
			EndDialog(hwnd, IDOK);
			return;
		}

		// The window does exist; update the time remaining.
		TCHAR szMsg[100];
		StringCchPrintf(szMsg, _countof(szMsg),
			TEXT("Your have %d seconds to respond"), --g_nSecLeft);
		SetDlgItemText(hwnd, ID_MSGBOX_STATIC_TEXT, szMsg);
	}
	else {

		// The window does not exist yet; do nothing this time.
		// We 'll try again in another second.
	}
}


int WINAPI _tWinMain(HINSTANCE, HINSTANCE, PTSTR, int) {

	_tcscpy_s(g_szCaption, _countof(g_szCaption), TEXT("Timed MEssage Box"));

	// How many seconds we'll give the user to respond
	g_nSecLeft = 10;

	// Create the threadpool timer object
	PTP_TIMER lpTimer =
		CreateThreadpoolTimer(MsgBoxTimeoutCallback, NULL, NULL);

	if (lpTimer == NULL) {
		TCHAR szMsg[MAX_PATH];
		StringCchPrintf(szMsg, _countof(szMsg),
			TEXT("Impossible to create the timer: %u"), GetLastError());
		MessageBox(NULL, szMsg, TEXT("Error"), MB_OK || MB_ICONERROR);

		return -1;
	}

	// Start the timer in one second to trigger every 1 second
	ULARGE_INTEGER ulRelativeStartTime;
	ulRelativeStartTime.QuadPart = (LONGLONG)-(10000000);	// start in 1 second
	FILETIME ftRelativeStartTime;
	ftRelativeStartTime.dwHighDateTime = ulRelativeStartTime.HighPart;
	ftRelativeStartTime.dwLowDateTime = ulRelativeStartTime.LowPart;
	SetThreadpoolTimer(
		lpTimer,
		&ftRelativeStartTime,
		1000,	// Triggers every 1000 milliseconds
		0);

	// Display the message box
	MessageBox(NULL, TEXT("You have 10 seconds to respond"),
		g_szCaption, MB_OK);

	// Clean up the timer
	CloseThreadpoolTimer(lpTimer);

	// Let us know if the user responed or if we timed out
	MessageBox(
		NULL, (g_nSecLeft == 1) ? TEXT("Timeout") : TEXT("User responded"),
		TEXT("REsult"), MB_OK);

	return 0;
}

注意事项:

线程池可能会调用多个线程来调用我们的回调函数(要做好线程同步)

如果线程池超负荷运行,可能会延误计时器的工作项。(比如线程池最大线程数设定为一个很低的值,那么线程池将不得不延误调用我们的回调函数)

如果工作想好时间较长,又不喜欢上诉行为,希望在每个工作项开始运行后的10秒钟将新的工作项添加到队列中,那么必须通过另一种途径来构造一种智能的一次性计时器。


1)仍然通过CreateThreadpoolTimer来创建计时器

2)调用SetThreadpoolTimer时给msPeriod参数传递0,表明这是一次性计时器

3)当处理工作完成后,重置计时器,仍然将msPeriod设为0

4)最后当最终需要停止计时器的时候,必须在CloseThreadpoolTimer执行前调用WaitForThreadpoolTimerCallbacks并传TRUE给最后一个参数,告知线程池不应该为该计时器处理任何工作项。

注意:真的需要一次性计时器则应该在回调函数中调用SetThreadpoolTimer并穿0给msPeriod, 同时为了清理线程池中的资源在回调函数中调用CloseThreadpoolTimer


11.3 情形3: 在内核对象触发时调用一个函数

可以创建一个工作项,让他在一个内核对象被触发的时候执行。首先创建一个回调函数

VOID CALLBACK WaitCallback(
	PTP_CALLBACK_INSTANCE pInstance,
	PVOID Context,
	PTP_WAIT Wait,
	TP_WAIT_RESULT WaitREsult);

调用CreateThreadpoolWait来创建一个线程池等待对象

WINBASEAPI
_Must_inspect_result_
PTP_WAIT
WINAPI
CreateThreadpoolWait(
    _In_ PTP_WAIT_CALLBACK pfnwa,
    _Inout_opt_ PVOID pv,
    _In_opt_ PTP_CALLBACK_ENVIRON pcbe
    );
接着就可以调用一下函数来讲一个内核对象绑定给这个等待对象并提交给线程池

WINBASEAPI
VOID
WINAPI
SetThreadpoolWait(
    _Inout_ PTP_WAIT pwa,
    _In_opt_ HANDLE h,
    _In_opt_ PFILETIME pftTimeout
    );

pwa是CreateThreadpoolWait返回的对象。

h是等待的内核对象。

pftTimeout用来表示线程池最长应该花多少时间来等待内核对象触发。 0表示不用等待, 负表示相对时间,正数表示绝对等待时间。

线程池在内部会调用WaitForMultipleObjects函数,传入通过SetThreadpoolWait函数注册的句柄,并穿FALSE给bWaitAll参数。

这样单任何一个句柄被触发的时候,线程池就会被唤醒。

由于WaitForMultipleObjects不允许多次传入同一个句柄,因此要确保SetThreadpoolWait不会注册同一个句柄。

但是可以调用DuplicateHandle,这样就可以为原始句柄创建副本。


当内核对象触发或等待超时的时候,线程池中的一个线程就会调用我们的WaitCallback函数。

最后一个WaitResult 类型为TP_WAIT_RESULT表明WaitCallback被调用的原因。


一旦线程池调用了注册的回调函数,对应的等待项将进入不活跃状态(inactive)。如果想让同一个回调函数在内核对象再次触发时候被调用需要调用

SetThreadpoolWait再次注册

另外SetThreadpoolWait再可以将同一个等待项和不同内核对象绑定。(在前一个内核对象已经触发以后)

也可以传入NULL将等待项从线程池中移出。

可以调用WaitForThreadpoolWaitCallbacks函数来等待一个等待项完成。

调用ClosethreadpoolWait来释放一个等待项的内存。



11.4 情形4:在异步I/O请求完成时调用一个函数

编写一个符合以下原型的函数

VOID CALLBACK OverlappedCompletionRoutine(
	PTP_CALLBACK_INSTANCE	pInstance,
	PVOID					pvContext,
	PVOID					POverlapped,
	ULONG					IoResult,
	ULONG_PTR				NumberOfBytesTransferred,
	PTP_IO					pIo);
当IO完成以后,这个函数会表调用并得到一个指向OVERLAPPED结构的指针(在调用ReadFile或者WrieFile时候传入)

操作结果通过IoResult传入如果IO成功,该参数为NO_ERROR.

以传输字节通过NumberOfBytesTransferred传入

pIO是一个指向线程池中的IO项指针


然后通过调用CreateThraedpoolIo来创建一个线程池IO对象,并将我们想要与线程池内部的IO完成端口相关联的文件句柄传入。

WINBASEAPI
_Must_inspect_result_
PTP_IO
WINAPI
CreateThreadpoolIo(
    _In_ HANDLE fl,
    _In_ PTP_WIN32_IO_CALLBACK pfnio,
    _Inout_opt_ PVOID pv,
    _In_opt_ PTP_CALLBACK_ENVIRON pcbe
    );

在创建好IO对象以后,可以通过下面函数将IO项中的文件设备与线程池内部的IO完成端口关联起来:

WINBASEAPI
VOID
WINAPI
StartThreadpoolIo(
    _Inout_ PTP_IO pio
    );

注意在每次调用ReadFile和WriteFile之前必须先调用StartThreadpolIo,如果没调用StartThreadpoolIo那么OverlappedCompletionRoutine回调函数不会被调用。

在发出IO请求后让线程池停止调用回调函数:


WINBASEAPI
VOID
WINAPI
CancelThreadpoolIo(
    _Inout_ PTP_IO pio
    );

在ReadFile或WriteFile调用失败的时候,仍然必须调用CancelThreadpoolIo。例如这两个函数返回FALSE并且调用GetLastError返回值为ERROR_IO_PENDING以外的值。


当文件设备使用完成以后调用CloseHandle将其关闭

WINBASEAPI
VOID
WINAPI
CloseThreadpoolIo(
    _Inout_ PTP_IO pio
    );

调用以下函数让另一个线程等待一个带处理的IO请求完成。

WINBASEAPI
VOID
WINAPI
WaitForThreadpoolIoCallbacks(
    _Inout_ PTP_IO pio,
    _In_ BOOL fCancelPendingCallbacks
    );

如果传fCancelPendingCallbacks给TRUE,当请求完成以后,回调函数并不会被调用。类似调用了CancelThreadpoolIo函数


11.5 回调函数的终止操作

线程池提供了一种便利的方法来描述在我们回调函数返回之后,应该执行的操作。采用pInstance参数(原型为 PTP_CALLBACK_INSTANCE)

可能调用一下参数:

WINBASEAPI
VOID
WINAPI
LeaveCriticalSectionWhenCallbackReturns(
    _Inout_ PTP_CALLBACK_INSTANCE pci,
    _Inout_ PCRITICAL_SECTION pcs
    );

WINBASEAPI
VOID
WINAPI
ReleaseMutexWhenCallbackReturns(
    _Inout_ PTP_CALLBACK_INSTANCE pci,
    _In_ HANDLE mut
    );

WINBASEAPI
VOID
WINAPI
ReleaseSemaphoreWhenCallbackReturns(
    _Inout_ PTP_CALLBACK_INSTANCE pci,
    _In_ HANDLE sem,
    _In_ DWORD crel
    );

WINBASEAPI
VOID
WINAPI
SetEventWhenCallbackReturns(
    _Inout_ PTP_CALLBACK_INSTANCE pci,
    _In_ HANDLE evt
    );


WINBASEAPI
VOID
WINAPI
FreeLibraryWhenCallbackReturns(
    _Inout_ PTP_CALLBACK_INSTANCE pci,
    _In_ HMODULE mod
    );



还有两个函数可以用于回调函数的实例

WINBASEAPI
BOOL
WINAPI
CallbackMayRunLong(
    _Inout_ PTP_CALLBACK_INSTANCE pci
    );

WINBASEAPI
VOID
WINAPI
DisassociateCurrentThreadFromCallback(
    _Inout_ PTP_CALLBACK_INSTANCE pci
    );

1)第一个函数通知线程池当前回调函数要执行长时间操作,返回TRUE说明线程池中还有空闲线程可以处理,返回FALSE说明线程池中没有空闲线程(这时候最好考虑将工作分割成更小的工作项)

2)第二个函数通知线程池当前工作项逻辑上已经完成,所有由于调用WaitForThreadpoolWorkCallbacks WaitForThreadpoolTimerCallbacks, WaitForThreadpoolWaitCallbacks或WaitForThreadpoolIoCalbacks而被阻塞的线程能够早一点返回,不必等到线程池的回调函数返回


11.5.1 对线程池的定制

在创建CreateThreadpoolxxx的时候会传入一个PTP_CALLBACK_ENVIRON参数

如果传入NULL会把工作项添加到默认的线程池中。

可以自己创建并定制线程池

WINBASEAPI
_Must_inspect_result_
PTP_POOL
WINAPI
CreateThreadpool(
    _Reserved_ PVOID reserved
    );

参数传入NULL

会返回一个线程池相关的PTP_POOL的值,然后就可以对线程池进行定制。

WINBASEAPI
BOOL
WINAPI
SetThreadpoolThreadMinimum(
    _Inout_ PTP_POOL ptpp,
    _In_ DWORD cthrdMic
    );

WINBASEAPI
VOID
WINAPI
SetThreadpoolThreadMaximum(
    _Inout_ PTP_POOL ptpp,
    _In_ DWORD cthrdMost
    );

某些应用场景需要常驻一些固定的线程,为了得到windows的通知。


当应用程序不再需要为自己定制线程池时,调用CloseThreadpool将其销毁


WINBASEAPI
VOID
WINAPI
CloseThreadpool(
    _Inout_ PTP_POOL ptpp
    );
线程池队列中所有未处理的项目将被取消。


一旦创建了自己的线程池,并指定了线程的最小数量和最大数量,就可以初始化一个回调环境(callback environment)。

typedef struct _TP_CALLBACK_ENVIRON_V3 {
    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                      Persistent   :  1;
            DWORD                      Private      : 30;
        } s;
    } u;
    TP_CALLBACK_PRIORITY               CallbackPriority;
    DWORD                              Size;
} TP_CALLBACK_ENVIRON_V3;

typedef TP_CALLBACK_ENVIRON_V3 TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;

对这个结构初始化

InitializeThreadpoolEnvironment(
    _Out_ PTP_CALLBACK_ENVIRON pcbe
    );

不再需要线程池回调环境的时候,调用一下函数将其清除。

VOID
DestroyThreadpoolEnvironment(
    _Inout_ PTP_CALLBACK_ENVIRON pcbe
    );
将线程池环境和自定义线程池绑定

VOID
SetThreadpoolCallbackPool(
    _Inout_ PTP_CALLBACK_ENVIRON pcbe,
    _In_    PTP_POOL             ptpp
    );

这样就可以用这个回调环境来添加工作项了。(如果不进行绑定,添加的工作项还是在默认的线程池中)


可以调用SetThreadpoolCallbackRunsLong函数来告诉回调环境,工作项需要较长时间处理。


VOID
SetThreadpoolCallbackRunsLong(
    _Inout_ PTP_CALLBACK_ENVIRON pcbe
    );

调用SetThreadCallbackLibrary来确保只要线程池中还有待处理的工作项,就将一个特定的DLL一直保持在进程的地址空间中。

VOID
SetThreadpoolCallbackLibrary(
    _Inout_ PTP_CALLBACK_ENVIRON pcbe,
    _In_    PVOID                mod
    );

11.5.2 得体地销毁线程池:清理组

对于自定义线程池,可以使用清理组来正确销毁。

1)首先创建一个清理组

WINBASEAPI
_Must_inspect_result_
PTP_CLEANUP_GROUP
WINAPI
CreateThreadpoolCleanupGroup(
    VOID
    );
然后将清理组合一个已经绑定了线程池的回调环境关联起来

VOID
SetThreadpoolCallbackCleanupGroup(
    _Inout_  PTP_CALLBACK_ENVIRON              pcbe,
    _In_     PTP_CLEANUP_GROUP                 ptpcg,
    _In_opt_ PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng
    );
pfng是一个回调函数地址。原型是

VOID CALLBACK CleanupGroupCancelCallback(
	PVOID pvObjectContext,
	PVOID pvCleanupContext);

接着CreateThreadpoolXXX 所创建的各种项 如果最后pcbe参数传入的是线程池回调环境,那么所创建的项会被添加到回调环境清理组中。

如果调用CloseThreadpoolXXX 则是隐式将对应项从清理组中移出。

也可以显示地释放所有清理组中添加的项

WINBASEAPI
VOID
WINAPI
CloseThreadpoolCleanupGroupMembers(
    _Inout_ PTP_CLEANUP_GROUP ptpcg,
    _In_ BOOL fCancelPendingCallbacks,
    _Inout_opt_ PVOID pvCleanupContext
    );

该函数会等待直到线程池中所有的项目都已经处理完毕。 也可以传递TRUE给fCancelPendingCallbacks,这样所有未处理的项目会被直接取消。

然后会调用之前SetThreadpoolCallbackCleanupGroup所传递的回调函数。没一个工作项都会调用一次回调函数。

如果传递FALSE给fCancelPendingCallbacks那么回答函数并不会被调用。

在所有工作项被取消或处理以后调用CloseThreadpoolCleanupGroup来释放清理组所占用的资源

WINBASEAPI
VOID
WINAPI
CloseThreadpoolCleanupGroup(
    _Inout_ PTP_CLEANUP_GROUP ptpcg
    );

最后释放线程池环境和自定义线程池

VOID
DestroyThreadpoolEnvironment(
    _Inout_ PTP_CALLBACK_ENVIRON pcbe
    );

WINBASEAPI
VOID
WINAPI
CloseThreadpool(
    _Inout_ PTP_POOL ptpp
    );




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值