Drecik学习经验分享
转载请注明出处:http://blog.csdn.net/drecik__/article/details/8194020
Windows线程提供了一个线程池机制来简化线程的创建,销毁以及日常管理,避免了线程频繁创建和销毁的开销。
一下介绍的与现场池有关的函数是新的线程池API,只能运行在Windwos Vista及以后版本。
这些线程池函数可以帮助我们做以下事情:
- 以异步的方式来调用一个函数
- 每隔一段时间调用一个函数
- 当内核对象出发的时候调用一个函数
- 当异步I/O请求完成的时候调用一个函数
1. 以异步的方式来调用一个函数
有两种方式实现该过程,下面分别讲述:
- 第一种方法,首先定义一个具有以下原型的函数:
为了让自己定义好的函数加入到线程池中,让线程执行我们的函数,我们需要向线程池提交请求:typedef VOID (NTAPI *PTP_SIMPLE_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, // 用户回调函数终止操作,我也没看懂,传NULL吧,一般操作都够用了; PVOID Context // 在加入线程池的时候由用户传入的参数; );
加入成功后,线程池就会有线程来执行我们的函数。BOOL TrySubmitThreadpoolCallback( PTP_SIMPLE_CALLBACK pfns, // 自己定义的函数; PVOID pv, // 传给自己定义函数的Context参数; PTP_CALLBACK_ENVIRON pcbe // 用来自己创建线程池,使用默认线程池传入NULL; );
- 第二种方法:由于每一次调用TrySubmitThreadpoolCallback系统内部的都会以我们的名义分配一个工作项,如果打算提交大量的工作项,那么可以处于性能和内存使用的考虑,创建工作性一次,然后分多次提交它会更好。
我们使用CreateThreadpoolWork创建一个工作项:PTP_WORK CreateThreadpoolWork( PTP_WORK_CALLBACK pfnwk, // 工作项调用的函数; PVOID pv, // 传给调用函数的参数; PTP_CALLBACK_ENVIRON pcbe ); // PTP_WORK_CALLBACK函数原型; typedef VOID (NTAPI *PTP_WORK_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, // 上面函数传进来的pv参数; PTP_WORK Work // 关联的工作项; ); // 向线程池提交一个请求的时候调用下面函数; // 提交成功后,线程池会有线程来执行我们创建的函数; VOID SubmitThreadpoolWork( PTP_WORK pwk // 工作项; );
多次提交唯一的缺点就是每一次传给自定义函数的Context值都相同,如果需要不同的值只能使用TrySubmitThreadpoolCallback函数。
如果我们有另外一个线程,该线程负责取消已经提交的工作项,或者等待工作项处理完毕而需要将自己挂起,则需要调用下面函数:
// 如果工作项未提交,函数就立即返回不执行任何操作; // 第二个参数如果为TRUE,那么该函数会试图取消先前提交的那个工作项; // 如果线程池中的线程正在处理那个工作项,该函数会一直等待到工作项完成后才返回; // 如果已经提交的工作项尚未被任何线程处理,那么会将它标记会取消,并立即返回,这样就不会被执行; // 如果为FALSE,那么将会挂起,知道指定工作项的处理已经完成,而且线程池处理该工作项的线程都已经被收回位置; // 如果用一个PTP_WORK对象提交了多个工作项,传给第二个参数为FALSE,就会等待所有工作项完成; // 如果为TRUE,只会等待当前正在运行的工作项; VOID WaitForThreadpoolWorkCallbacks( PTP_WORK pwk, // 待取消或等待的工作项; BOOL fCancelPendingCallbacks ); // 最后一个函数关闭创建的工作项; VOID CloseThreadpoolWork( PTP_WORK pwk );
2. 每个一段时间调用一个函数
① 首先我们必须定义一个如下原型的回调函数:② 创建一个PTP_TIMER对象:typedef VOID (NTAPI *PTP_TIMER_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, // 传入的参数; PTP_TIMER Timer // 关联的TIME指针; );
③ 注册计时器:PTP_TIMER CreateThreadpoolTimer( PTP_TIMER_CALLBACK pfnti, // 自己定义的函数指针; PVOID pv, // 传入自己定义的函数的参数; PTP_CALLBACK_ENVIRON pcbe );
设置了计时器之后,还可以调用SetThreadpoolTimer来对计时器进行修改,也可以在pftDueTimer传入NULL表示停止调用回调函数。// 其中开始时间传入负值表示从现在开始多长时间第一次触发; // 如果为正值,表示一个绝对时间,从1600年1月1日开始计算; // 单位都为100纳秒; VOID SetThreadpoolTimer( PTP_TIMER pti, // 创建的计时器对象; PFILETIME pftDueTime, // 开始时间; DWORD msPeriod, // 每隔多少时间触发一次,只触发一次传入0; DWORD msWindowLength // 该参数可以指定在msPeriod+msWindowLength这一时间段内触发,防止冲突; );
通过IsThreadpoolTimerSet来判断某个计时器是否被设置。
最后两个函数WaitForThreadpoolTimerCallbacks和CloseThradpoolTimer与之前讨论过的类似,不在讨论。
3. 在内核对象出发时调用一个函数
这些线程池操作基本上都大同小异,所以有些参数不在解释:
// 回调函数; // 第四个参数表示事件触发的类型,WAIT_OBJECT_0:关联的事件触发; // WAIT_TIMEOUT:等待超时,WAIT_ABANDONED_0:互斥量遗弃(参考以前的博文); VOID (NTAPI *PTP_WAIT_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult ); PTP_WAIT CreateThreadpoolWait( PTP_WAIT_CALLBACK pfnwa, PVOID pv, PTP_CALLBACK_ENVIRON pcbe ); // 关联内核对象; // 可多次调用关联多个事件,但其中一个触发就会调用回调函数; // 第三个参数表示等待时间,0表示不等待,NULL表示无限等待,正数表示绝对时间,负数相对时间; VOID SetThreadpoolWait( PTP_WAIT pwa, HANDLE h, // 内核对象; PFILETIME pftTimeout );
一旦调用回调函数,对应的等待项将不活跃,必须重新关联该内核对象。
可以在SetThreadpoolWait事件传入NULL表示将该等待项从内存池中移除。
最后WaitForThreadpoolWaitCallbacks等待等待项完成,CloseThreadpoolWait释放等待项内存。
最好不要使用PulseEvent触发事件,因为无法保证线程池正好在等待该事件。
4. 在异步I/O请求完成时调用一个函数
同样还有WaitForThreadpoolIoCallbacks来等待请求的完成。typedef VOID (WINAPI *PTP_WIN32_IO_CALLBACK)( PTP_CALLBACK_INSTANCE Instance, PVOID Context, PVOID Overlapped, // 异步IO传入的OVERLAPPED结构指针; ULONG IoResult, // 是否有错误,NO_ERROR表示没有错误; ULONG_PTR NumberOfBytesTransferred, // 传输的字节; PTP_IO Io ); PTP_IO CreateThreadpoolIo( HANDLE fl, // 设备句柄; PTP_WIN32_IO_CALLBACK pfnio, // 回调函数; PVOID pv, PTP_CALLBACK_ENVIRON pcbe ); // 每次发送异步I/O请求必须与线程池关联; VOID StartThreadpoolIo( PTP_IO pio ); // 在发出请求之后让线程停止调用回调函数; // 如果发送请求的时候调用失败,则仍然必须调用下面函数; VOID CancelThreadpoolIo( PTP_IO pio ); // 在关闭设备对象的时候,解除与内存池的关联; VOID CloseThreadpoolIo( PTP_IO pio );