第十一章:线程池

 

1. Windows提供了一个(与I/O完成端口相配套的)线程池机制来简化线程的创建,销毁以及日常管理.这个线程池函数,允许我们做以下事情:

● 以异步的方式来调用一个函数

● 每隔一段时间调用一个函数

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

● 当异步I/O请求完成的时候调用一个函数

2. 以异步方式来调用函数

VOID NTAPI SimpleCallback(

PTP_CALLBACK_INSTANCE pInstance,

PVOID pvContext);

BOOL TrySubmitThreadpoolCallback(

PTP_SIMPLE_CALLBACK pfnCallback,

PVOID pvContext,//SimpleCallback函数的参数

PTP_CALLBACK_ENVIRON pcbe);

功能:该函数(通过调用PostQueuedCompletionStatus来)将一个工作项添加到线程池的队列中,若调用成功,则返回TRUE,在调用TrySubmitThreadpoolCallback的时候,pfnCallback参数用来标示我们编写的那个符合SimpleCallback原型的函数.

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

创建工作项:

PTP_WORK CreateThreadpoolWork(

PTP_WORK_CALLBACK pfnWorkHandler,//函数指针(回调函数)形式如下:

VOID CALLBACK WorkCallback(

PTP_CALLBACK_INSTANCE instance,

PVOID Context,

PTP_WORK Work)

PVOID pvContext, //函数参数

PTP_CALLBACK_ENVIRON pcbe);

功能:在用户模式下创建一个结构来保存它的三个参数,并返回指向改结构的指针.

当我们想要向线程池提交一个请求时,可以调用SubmitThreadpoolWork函数:

VOID SubmitThreadpoolWork(PTP_WORK pWork);

这样就假定成功的将请求添加到队列中(也就是通过线程池中的线程来调用回调函数).

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

VOID WaitForThreadpoolWorkCallbacks(

PTP_WORK pWork,//前一函数返回值

BOOL bCancelPendingCallbacks);//标志位

函数功能:bCancelPendingCallback:函数会试图取消指定的工作项.如果此时工作项正在处理,则该函数将等待处理完成返回.如果还未处理,那么函数会先将它标记为已取消,然后立即返回.

如果传FALSE,那么函数会将调用线程挂起,直到指定工作项的处理已经完成,而且线程池中处理该工作项的线程也已经被回收并准备处理下一个工作项为止.

如果PTP_WORK对象提交了多个工作项,而且bCancelPendingCallback参数值为FALSE,那么函数会等待线程池处理所有已提交的工作项.如果为TRUE,那么函数只会等待当前正在运行的工作项完成.

如果我们不需要工作项的时候,可以调用CloseThreadpoolWork.

VOID CloseThreadpoolWork(PTP_WORK pwk);

3. 每隔一段时间调用一个函数

PTP_TIMER CreateThreadpoolTimer(

PTP_TIMER_CALLBACK pfnTimerCallback,//回调函数形式如下:

VOID CALLBACK_INSTANCE TimeoutCallback( 

PTP_CALLBACK_INSTANCE pInstance,

PVOID pvContext,

PTP_TIMER pTimer);//由Create...传递

PVOID pvContext,

PTP_CALLBACK_ENVIRON pcbe);

注册计时器:

VOID SetThreadpoolTimer(

PTP_TIMER pTimer,

PFILETIME pftDueTime,//第一次调用回调时间,为负值表示间隔时间,-1表示立即开始

DWORD msPeriod, //调用回调函数的间隔(为0表示只触发一次)

DWORD msWindowLength);//为当前设定的出发时间加msWindowLength随机值.

函数注解:

如果想要重新设计定时器的开始时间和间隔时间.我们可以为后面三个数设置新值.如果将pftDueTime传NULL,那么计时器将暂停,而不必销毁它.

查看计时器是否已经被设置:

BOOL IsThreadpoolTimerSet( PTP_TIMER);

其他的函数有WaitForThreadpoolCallbacks来等待完成,CloseThreadpoolTimer来释放计时器的内存.

4. 在内核对象触发时调用一个函数

创建函数:

PTP_WAIT CreateThreadpoolWait(

PTP_WAIT_CALLBACK pfnWaitCallback,

PVOID pvContext,

PTP_CALLBACK_ENVIRON pcbe);

其中pfnWaitCallback的形式如下:

VOID CALLBACK WaitCallback(

PTP_CALLBACK_INSTANCE pInstance,

PVOID Context,

PTP_WAIT Wait,//回调函数的原因(也称作状态)

TP_WAIT_RESULT WaitResult)

绑定内核对象到线程池:

VOID SetThreadpoolWait(

PTP_WAIT pWaitItem,//创建返回的值

HANDLE hObject,//内核对象

PFILETIME pfnTimeout);//等待时间.

pfnTimeout:表示线程池最长应该花多少时间来等待该内核对象被触发.0表示不用等待,传赋值表示相对时间.传正值表示绝对时间.传NULL表示无限长的时间.

在使用该函数等待的时候,要确保不会多次注册同一个句柄.

WaitCallback的取值如下:

WaitResult的值

解释

WAIT_OBJECT_0

如果传给SetThreadpoolWait的内核对象在超时之前被触发,那么我们的回调函数会接收到这个值

WAIT_TIMEOUT

如果传给SetThreadpoolWait的内核对象在超时之前没被触发,那么我们会接收到这个值

WAIT_ABANDONED_0

如果传给SetThreadpoolWait的内核对象时一个互斥量并且该互斥量被遗弃,那么我们就会接收到这个值

一旦线程池的一个线程调用了我们的回调函数,对应的等待项将不进入不活跃状态.(也就是无法被再次调用,除非再调用SetThreadpoolWait).如果我们传NULL给该等待项以从线程池中移除.

5. 在异步I/O请求完成时调用一个函数

创建线程池的I/O对象:

PTP_IO CreateThreadpoolIo(

HANDLE hDevice,//通过CreateFile传入FILE_FLAG_OVERLAPPED标志

PTP_WIN32_IO_CALLBACK pfnIoCallback,//回调函数

PVOID pvContext,//参数

PTP_CALLBACK_ENVIRON pcbe);

回调函数如下所示:

VOID CALLBACK OverlappedCompletionRoutine(

PTP_CALLBACK_INSTANCE pInstance,

PVOID pvContext,

PVOID pOverlapped,

ULONG IoResult,//操作结果(成功为NO_ERROR)

ULONG_PTR NumberofBytesTransferred,//已传入的字节数

PTP_IO pIo);//指向线程池中的I/O项的指针.

关联嵌入在I/O项中的文件/设备与线程池内部的I/O完成端口关联起来:

VOID StartThreadpoolIo( PTP_IO pio);

注意,这个函数必须要在ReadFile和WriteFile前调用

停止调用我们的回调函数:

VOID CancelThreadpoolIo(PTP_IO pio);

我们也可以让另一个线程等待一个待处理的I/O请求完成:

VOID WaitForThreadpoolIoCallbacks(

PTP_IO pio,

BOOL bCancelPendingCallbacks);//如果为TRUE,那么当请求完成的时候,我们的回调函数不会被调用(如果它尚未调用).

6. 回调函数的终止操作

线程池提供了一个钟便利的方法,用来描述在women的回调函数返回之后,应该执行的一些操作.回调函数用传给它的不透明的pInstance参数(类型为PTP_CALLBACK_INSTANCE)来调用以下这些函数:

VOID LeaveCriticalSectionWhenCallbackReturns(

PTP_CALLBACK_INSTANCE pci, PCRITICAL_SECTION pcs)

VOID ReleaseMutexWhenCallbcakReturns(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);

这些函数都是当回调函数返回的时候,线程池会自动调用相应的函数使得其进入触发状态.并传入指定的结构.我们无法要求线程池在处理完我们的工作项后同时触发一个事件和互斥量,最后调用的终止函数会覆盖之前调用的那个终止函数.

另外两个终止函数:

BOOL CallbcakMayRunLong(PTP_CALLBACK_INSTANCE pci);//主要是用来通知线程池回调函数的运行时间会比较长.函数返回TRUE表示还有其他线程可供使用.

VOID DisassociateCurrentThreadFromCallback(PTP_CALLBACK_INSTANCE pci);//告诉线程池,逻辑上自己已经完成了工作.

如果我们想要对线程池进行定制.那么我们可以调用下面的函数来创建一个线程池:

PTP_POOL CreateThreadpool( PVOID reserved);//reserved为保留,为NULL

设置线程池中线程的最大数量和最小数量:

BOOL SetThreadpoolMinimum( PTP_BOOL pThreadPool,DWORD cthrdMin);//>=1

BOOL SetThreadpoolMaximum( PTP_BOOL pThreadPool,DWORD cthrdMost);//<=500

初始化回调环境(包含了一些可应用于工作项的额外的设置或配置)函数:

typedef struct _TP_CALLBACK_ENVIRON {      

ULONG Version;      

_TP_POOL * Pool;      

_TP_CLEANUP_GROUP *  CleanupGroup;      

PVOID CleanupGroupCancelCallback;      

PVOID  RaceDll;      

_ACTIVATION_CONTEXT * ActivationContext;      

PVOID  FinalizationCallback;      

ULONG u;

} TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;

虽然定义了这些字段,但是我们应该将它看成是不透明的.

和该结构有关的一系列函数:

初始化结构:

Inline VOID InitializeThreadpoolEnvironment(

PTP_CALLBACK_ENVIRON pcbe );//version设为1,其余字段设为0

注销结构:

Void DestroyThreadpoolEnvironment(

PTP_CALLBACK_ENVIRON pcbe);//需要注意的是,为了将一个工作项添加到线程池队列中,回调环境必须表明该工作项应该由哪个线程池来处理.:

Void SetThreadpoolCallbackPool(PTP_CALLBACK_ENVIRON pcbe,

PTP_POOL pThreadPool);

如果我们不调用它,系统将会使用默认的线程池来处理它.

告诉回调环境,工作项通常需要较长时间来处理.这会使得线程池更快的创建线程.

Void SetThreadpoolCallbackRunsLong(PTP_CALLBACK_ENVIRON pcbe);

如果需要线程池对应的DLL在线程池没有停止之前一直在内存中:

VOID SetThreadpoolCallbackLibrary( PTP_CALLBACK_ENVIRON pcbe);

7. 清理组:非默认线程池的清理工作.

◆ 创建清理组:

PTP_CLEANUP_GROUP CreateThreadpoolCleanupGroup();

◆ 绑定线程池到清理组:

VOID SetThreadpoolCallbackCleanupGroup(

PTP_CALLBACK_ENVIRON pcbe,

PTP_CLEANUP_GROUP ptpcg,

PTP_CLEANUP_GROUP_CALLBACK pfng);//指向一个回调函数的地址,如果清理组被取消,那么这个函数会被调用.如果pfng不为NULL,则应该为如下形式:

VOID CALLBACK CleanupGroupCancelCallback(

PVOID pvObjectContext,

PVOID pvCleanupContext);

◆ 应用程序想要销毁线程池:

VOID CloseThreadpoolCleanupGroupMembers(

PTP_CLEANUP_GROUP ptpcg,

BOOL bCancelPendingCallbacks,

PVOID pvCleanupContext);

函数的使用:

如果bCancelPendingCallbacks为TRUE,将所有已经提交,但是尚未处理的工作项直接取消.函数会在所有当前正在运行的工作项完成之后返回.

如果bCancelPendingCallbacks为TRUE,而且pfng为CleanupGroupCallback函数的地址.那么每次取消就会调用一次回调函数.参数pvCleanupContext包含取消项的上下文.

如果bCancelPendingCallbacks传入FALSE.函数返回之前,线程池会花时间来处理所有剩下的项.此时回调函数也不会被调用.

释放清理组所占的资源:

VOID WINAPI CloseThreadpoolCleanupGroup( pTP_CLEANUP_GROUPptpcg);

你可能感兴趣的:(第十一章:线程池)