《Windows核心编程系列》谈谈Windows线程池
WAIT_OBJECT_0 超时之前有对象被触发。
WAIT_TIMEOUT 由于超时导致回调函数被触发。
WAIT_ABANDONED_0 如果传入的内核对象是互斥量且被遗弃。回调函数将收到这个值。
一旦线程池调用了我们的回调函数,对应的等待项将进入不活跃状态。所谓不活跃状态:如果想让回调函数在同一个内核对象被触发时再次被调用,我们需要调用SetThreadpoolWait来再次注册。
最后我们同样可以等待一个等待项完成。这可以调用WaitForThreadpoolWaitCallbacks。还可以调用CloseThreadpoolWait来释放一个等待项的内存。
注意:不要让回调函数调用WaitForThreadpoolWork并将自己的工作项作为参数传入,这会导致死锁。
情形四:在异步IO完成时调用一个函数。
我们在上一篇博文中介绍了如何使用IO完成端口来高效的执行异步IO操作,也介绍了如何创建一个线程池并让其中的线程等待IO完成端口。这里我们将介绍线程池如何管理线程的创建和销毁。
在打开一个关联起来文件或设备时,我们必须现将该设备与线程池的IO完成端口,然后告诉线程池在异步IO完成时应该调用哪个函数。
首先我们需要定义回调函数,它需要满足一下原型:
VOID CALLBACK OverlappedCompletionRoutine( PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PVOID pOverlapped, ULONG IoResult; ULONG_PTR NumberOfBytesTransferred, PTP_IO pIo);
当一个IO操作完成时此回调函数会被调用并得到一个指向OVERLAPPED结构的指针。此结构是我们在调用ReadFile后WriteFile时传入的。
IoResult表示IO异步操作的执行结果。如果IO请求成功,将传给回调函数NO_ERROR。
NumberOfBytesTransferred参数传入已传输的字节数。
pIo传入指向线程池IO项的指针。马上介绍。
pInstance后面会有介绍。
定义好回调函数后,我们就需要调用CreateThreadpoolIo来创建一个线程池IO对象。
PTP_IO CreateThreadpoolIo( HANDLE hDevice, PTP_WIN32_IO_CALLBACK pfnIoCallback, PVOID pvContext, PTP_CALLBACK_ENVIRON pcbe);
hDevice是与IO对象相关联的设备句柄。
pfnIoCallback是前面我们介绍的回调函数指针。
pvContext当然是传给回调函数的参数。
当IO对象创建好之后,我们就可以通过下面的函数来将潜入在IO项的设备与IO完成端口关联起来。
VOID StartThreadpoolIo(PTP_IO pio);
关联之后我们就可以调用ReadFile或WriteFile了。此后当异步IO请求完成后,回调函数将会被调用。
此外我们还可以调用以下函数来停止线程池调用回调函数,此后回调函数将不会被调用:
VOID CancelThreadpoolIo(PTP_IO pio);
CloseThreadpoolIo将取消设备与线程池的关联:
VOID CloseHandlepoolIo(PTP_IO pio);
WaitForThreadpoolIoCallbacks将等待一个待处理的IO请求我完成。
VOID WaitForThreadpoolIoCallback( PTP_IO pio, BOOL bCancelPendingCallbacks);
如果传给bCancelPendingCallbacks的值为true,那么当请求完成时,回调函数不会被调用。
对线程池进行定制
在调用CreateThreadpoolWork、CreateThreadpoolTimer,CreateThreadpoolWait或CreateThreadpoolIo时,有一个PTP_CALLBACK_ENVIRON类型的参数。如果传给它NULL则表示我们会将工作项添加到默认的线程池中。一般情况下默认的线程池能够满足大多数情况下的要求。
如果我们想定制我们自己的线程池,可以调用CreateThreadpool来创建新线程池:
PTP_POOL CreateThreadpool(PVOID reserved);
Reserved是保留的,传入NULL即可。
该函数返回一个PTP_POOL值,它表示新创建的线程。
此后我们就可以设置线程池中的最大线程和最小线程了。默认的线程池中线程最少为1