windows核心编程---windows线程池

来源:互联网 发布:docker github 源码 编辑:程序博客网 时间:2024/05/22 10:40

-概述
1.
I/O完成端口只对等待它的线程进行分派,创建和销毁线程的工作仍然必须由我们自己完成。
对如何创建和销毁线程,每个人有自己的意见。
为了简化开发人员的工作,windows提供了一个(与I/O完成端口相配套的)线程池机制来简化线程的创建,销毁以及日常管理。

这个新的通用线程池可能并不适用于所有的情况,但很多情况下它能够满足我们的需要并节省大量的开发时间。

2.这些新的线程池函数允许我们做以下事情:
以异步的方式来调用一个函数
每隔一段时间调用一个函数
当内核对象触发的时候调用一个函数
当异步I/O请求完成的时候调用一个函数

3.
一个进程初始化时,它并没有任何与线程池有关的开销。
但是,一旦调用了新的线程池函数,系统就会为进程创建相应的内核资源。其中一些资源在进程终止前都将一直存在。
使用线程池的开销取决于用法:系统会以进程的名义来分配线程,其它内核对象以及内部数据结构。

-以异步方式调用函数

// 让线程池以异步方式执行一个函数VOID NTAPI SimpleCallback(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext);// 通过PostQueuedCompletionStatus来将一个工作项添加到线程池的队列中,成功,返回TRUE。BOOL TrySubmitThreadpoolCallback(// 回调函数PTP_SIMPLE_CALLBACK pfnCallback,// 参数PVOID pvContext,PTP_CALLBACK_ENVIRON pcbe);

系统会自动为我们的进程创建一个默认的线程池,并让线程池中的一个线程来调用我们的回调函数。当这个线程处理完一个客户请求后,不会立即被销毁,而是会回到线程池,准备好处理队列中的其它工作项。线程池会不断重复使用其中的线程。如果线程池检测到创建另一个线程将能够更好地为应用程序服务,那它就会这样做。如线程池检测到它的线程数量已供过于求,那么它就会销毁其中一些。

-显式地控制工作项
某些情况下,如内存不足或配额限制,TrySubmitThreadpoolCallback可能会失败。
每次调用TrySubmitThreadpoolCallback时,系统会在内部以我们的名义分配一个工作项。

VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance,PVOID Context,PTP_WORK Work);// 会在用户模式内存中创建一个结构来保存它的三个参数,并返回指向该结构的指针。PTP_WORK CreateThreadpoolWork(// 函数指针,线程池中的线程最终对工作项处理时,会调用该函数指针指向的函数PTP_WORK_CALLBACK pfnWorkHandler,// 传给回调函数的值PVOID pvContext,PTP_CALLBACK_ENVIRON pcbe);// 向线程池提交一个请求VOID SubmitThreadpoolWork(PTP_WORK pWork);// 另一个线程,想要取消已经提交的工作项,或该线程由于要等待工作项处理完毕而需要将自己挂起。// 如指定的工作项尚未提交,直接返回。VOID WaitForThreadpoolWorkCallbacks(PTP_WORK pWork,// TRUE,// 指定的工作项尚未被处理,函数将其标记为取消并返回。// 指定的工作项正在被处理,等待处理完成后,再返回。// 同一工作项被提交多次时,只等待当前正处理的。// FALSE// 指定的工作项被处理完且处理它的线程已经被收回并准备处理下一个工作项时,才返回。// 同一工作项被提交多次时,等待所有均被处理后,返回。BOOL bCancelPendingCallbacks);// 关闭工作项VOID CloseThreadpoolWork(PTP_WORK pwk);

-每隔一段时间调用一个函数
windows提供了可等待的计时器内核对象。线程池函数可实现类似的功能。

// 回调函数VOID CALLBACK TimeoutCallback(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_TIMER pTimer);// 通知线程池在何时调用我们的函数PTP_TIMER CreateThreadpoolTimer(PTP_TIMER_CALLBACK pfnTimerCallback,PVOID pvContext,PTP_CALLBACK_ENVIRON pcbe);// 向线程池注册计时器VOID SetThreadpoolTimer(PTP_TIMER pTimer,// 开始时间。// 正值,绝对时间。100纳秒为单位,始于1600.1.1// 负值,来指定相对时间。// -1,立即开始。// NULL,暂停。PFILETIME pftDueTime,// 间隔DWORD msPeriod,// 使回调函数会在当前设定的触发时间 + (该时间+msWindowLength)时间间触发DWORD msWindowLength);// 确定计时器是否已被设置BOOL IsThreadpoolTimerSet(PTP_TIMER pti);// 等待WaitForThreadpoolTimerCallbacks// 关闭CloseThreadpoolTimer

在设置了计时器之后,还可以通过调用SetThreadpoolTimer并在pTimer参数中传入先前设置的计时器指针,来对已有的计时器进行修改。
如果线程池超负荷运行,可能会延误计时器工作项。

-在内核对象触发时,调用一个函数
注册一个工作项,让它在一个内核对象被触发的时候执行。

VOID CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE pInstance,PVOID Context,PTP_WAIT Wait,TP_WAIT_RESULT WaitResult);PTP_WAIT CreateThreadpoolWait(PTP_WAIT_CALLBACK pfnWaitCallback,PVOID pvContext,PTP_CALLBACK_ENVIRON pcbe);VOID SetThreadpoolWait(PTP_WAIT pWaitItem,HANDLE hObject,// 0 不用等待// 负值,相对时间// 正值,绝对时间// NULL,无限长PFILETIME pftTimeout);// 等待WaitForThreadpoolWaitCallbacks// 关闭CloseThreadpoolWait

当hObject标识的内核对象被触发时,会导致线程池调用我们的WaitCallback。参数pftTimeout来表示线程池最长应该化多少时间来等待该内核对象被触发。

线程池在内部会让一个线程调用WaitForMultipleObjects,传入SetThreadpoolWait函数注册的一组句柄,并传FALSE给bWaitAll参数。这样当任何一个句柄被触发的时候,线程池就会被唤醒。
WaitForMultipleObjects不允许我们将同一个句柄传入多次,因此,我们须确保不会用SetThreadpoolWait来多次注册同一个句柄。但可调用DuplicateHandle来分别注册原始句柄和复制的句柄。

当内核对象被触发或超出等待时间的时候,线程池中的某个线程会调用我们的WaitCallback。WaitCallback的TP_WAIT_RESULT参数来表示回调函数被调用的原因。
WAIT_OBJECT_0
WAIT_TIMEOUT
WAIT_ABANDONED_0

一旦线程池的一个线程调用了我们的回调函数,对应的等待项将进入不活跃状态。不活跃意味着如果想让回调函数在同一个内核对象被触发的时候再次被调用,那么我们需调用SetThreadpoolWait来再次注册。

-在异步I/O请求完成时,调用一个函数
本章通篇介绍的线程池函数可以替我们管理线程的创建和销毁,而这些线程会在内部等待I/O完成端口。
要求,
打开文件或设备时,须先将该文件/设备与线程池的I/O完成端口关联起来。
须告诉线程池,当发往文件/设备的异步I/O请求完成时,应该调用那个函数。

VOID CALLBACK OverlappedCompletionRoutine(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PVOID pOverlapped,ULONG IoResult,ULONG_PTR NumberOfBytesTransferred,PTP_IO pIo);

当一个I/O操作完成的时候,上述函数会被调用并得到一个指向OVERLAPPED结构的指针,这个指针是在调用ReadFile或WriteFile来发出I/O请求的时候传入的。操作的结果通过IoResult参数传入,如成功,该参数为NO_ERROR。已传输字节数通过NumberOfBytesTransferred传入。pIo,一个指向线程池中的I/O项的指针。

PTP_IO CreateThreadpoolIo(HANDLE hDevice,PTP_WIN32_IO_CALLBACK pfnIoCallback,PVOID pvContext,PTP_CALLBACK_ENVIRON pcbe);

线程池I/O对象创建完毕后,用下面函数来将嵌入在I/O项中的文件/设备与线程池内部的I/O完成端口关联起来。

VOID StartThreadpoolIo(PTP_IO pio);

在每次调用ReadFile和WriteFile前,必须调用StartThreadpoolIo。如果每次在发出I/O请求前没有调用StartThreadpoolIo,那么OverlappedCompletionRoutine回调函数将不会被调用。

VOID WaitForThreadpoolIoCallbacks(PTP_IO pio,BOOL bCancelPendingCallbacks);// 在发出I/O请求后,让线程池停止调用我们的回调函数VOID CancelThreadpoolIo(PTP_IO pio);// 关闭VOID CloseThreadpoolIo(PTP_IO pio);

-回调函数的终止操作
线程池提供了一种便利的方法,来描述我们的回调函数返回后,应该执行的一些操作。回调函数用传给它的不透明的pInstance来调用以下这些函数:

VOID LeaveCriticalSectionWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci,PCRITICAL_SECTION pcs);VOID ReleaseMutexWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci,HANDLE mut);VOID ReleaseSemaphoreWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci,HANDLE sem,DWORD crel);VOID SetEventWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci,HANDLE evt);VOID FreeLibraryWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci,HMODULE mod);

对任何一个回调函数的实例,线程池中的线程只会执行一种终止操作。
除了这些终止函数外,还有两个函数可用于回调函数的实例:

// 回调函数通知线程池,自己需较长时间来处理当前的项// 如返回TRUE,说明线程池中还有其它线程可供使用,来对队列中的项进行处理。// 如返回FALSE,说明线程池中没有其它线程可用来处理队列中的项。此时,最好将任务拆分为多次完成。BOOL CallbackMayRunLong(PTP_CALLBACK_INSTANCE pci);// 回调函数用它来告诉线程池,逻辑上自己已完成了工作。// 使调用WaitForThreadpoolxxCallbacks而被阻塞的线程能早一些返回。VOID DisassociateCurrentThreadFromCallback(PTP_CALLBACK_INSTANCE pci);

-对线程池进行定制
在调用CreateThreadpoolWork,CreateThreadpoolTimer,CreateThreadpoolWait或CreateThreadpoolIo时,有机会传入一个PTP_CALLBACK_ENVIRON参数。如传NULL,会将工作项添加到进程默认的线程池中。

1.创建新的线程池

PTP_POOL CreateThreadpool(// 保留,传NULL。PVOID reserved);// 设置线程池中线程最大,最小数量BOOL SetThreadpoolThreadMinimum(PTP_POOL pThreadPool,DWORD cthrdMin);BOOL SetThreadpoolThreadMaximum(PTP_POOL pThreadPool,DWORD cthrdMost);// 关闭线程池。// 线程池中线程结束本次处理后结束。// 线程池队列中尚未开始处理的项将被取消。VOID CloseThreadpool(PTP_POOL pThreadPool);

线程池始终保持池中的线程数量至少是最小数量,并允许线程数量增长到指定的最大数量。

2.回调环境
一旦我们创建了自己的线程池,并指定了线程的最小数量和最大数量,我们就可初始化一个回调环境,它包含了一些可用于工作项的额外的设置或配置。

线程池回调环境数据结构

typedef struct _TP_CALLBACK_ENVIRON{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 Private : 31;}s;}u;}TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;// 初始化VOID InitializeThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe);// 清理VOID DestroyThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe);// 为了将一个工作项添加到线程池的队列中,回调环境必须标明该工作项应由那个线程池来处理。不调用时,pcbe初始化后的Pool字段为NULL,添加到进程默认的线程池。VOID SetThreadpoolCallbackPool(PTP_CALLBACK_ENVIRON pcbe,PTP_POOL pThreadPool);// 确保,只要线程池中还有待处理的工作项,就将一个特定的DLL一直保持在进程的地址空间中。VOID SetThreadpoolCallbackLibrary(PTP_CALLBACK_ENVIRON pcbe,PVOID mod);

-得体地销毁线程池:清理组
线程池可处理大量的队列项,这些项的来源各不相同。
这使我们很难知道线程池结束处理队列项的确切时间,但只有这样才能得体地将它销毁。
为了帮助我们对线程池进行得体的清理,线程池提供了清理组。
默认的线程池不会被销毁。在进程终止时,windows会将其销毁并负责所有的清理工作。
1.
创建一个清理组

PTP_CLEANUP_GROUP CreateThreadpoolCleanupGroup(); 

2.
将清理组与一个已经绑定到线程池的TP_CALLBACK_ENVIRON结构关联。

VOID SetThreadpoolCallbackCleanupGroup(PTP_CALLBACK_ENVIRON pcbe,PTP_CLEANUP_GROUP ptpcg,// 清理组被取消时,回调函数会被调用PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng);VOID CALLBACK CleanupGroupCancelCallback(PVOID pvObjectContext,PVOID pvCleanupContext);

这个函数在内部会设置PTP_CALLBACK_ENVIRON的CleanupGroup字段和CleanupGroupCancelCallback字段。

当调用CreateThreadpoolWork,CreateThreadpoolTimer,CreateThreadpoolWait,CreateThreadpoolIo时,如最后的的参数,即指向PTP_CALLBACK_ENVIRON结构的指针不为NULL,那么创建的项会被添加到对应的回调环境的清理组中。

在这些队列项完成后,如我们调用了CloseThreadpoolWork,CloseThreadpoolTimer,CloseThreadpoolWait和CloseThreadpoolIo,等于是隐式地将对应的项从清理组中移除。

3.
销毁线程池

// 函数会一直等待,直到线程池的工作组中所有剩余的项(创建但未关闭的)都已处理完毕。VOID CloseThreadpoolCleanupGroupMembers(PTP_CLEANUP_GROUP ptpcg,// TRUE,在所有当前正在运行的工作项完成后返回。尚未处理的取消。// FALSE,等待所有项处理完毕BOOL bCancelPendingCallbacks,PVOID pvCleanupContext);

如果传给bCancelPendingCallbacks值为TRUE,且传给SetThreadpoolCallbackCleanupGroup的pfng不为NULL。则,对每个被取消的工作项,pfng会被调用。回调函数的pvObjectContext包含每个被取消项的上下文(CreateThreadpoolxxx时设置的)。回调函数的pvCleanupContext是上述调用的最后一个参数。

4.释放清理组

VOID WINAPI CloseThreadpoolCleanupGroup(PTP_CLEANUP_GROUP ptpcg);// 销毁自定义线程池的环境DestroyThreadpoolEnvironment// 关闭线程池CloseThreadpool
原创粉丝点击