Windows核心编程(五)

十一、Windows线程池

Windows提供了一个(与I/O完成端口相配套的)线程池机制来简化线程的创建、销毁以及日常管理。这个通用的线程池可能并不适用于所有的情况,但很多情况下能够满足需要并节省大量的开发时间。

这些新的线程池函数允许我们做以下这些事情:

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

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

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

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

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

1、以异步方式调用函数

为了让线程池中的一个线程执行调用函数,需要向线程池提交一个请求,为此只需要调用下面的函数:

BOOL TrySubmitThreadpoolCallback(
    __in        PTP_SIMPLE_CALLBACK  pfns,
    __inout_opt PVOID                pv,
    __in_opt    PTP_CALLBACK_ENVIRON pcbe
    );

该函数(通过PostQueuedCompletionStatus)将一个工作项(work item)添加到线程池的队列中。系统会自动为我们的进程创建一个默认的线程池,并让线程池中的一个线程来调用回调函数。

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

PTP_WORK CreateThreadpoolWork(
    __in        PTP_WORK_CALLBACK    pfnwk,
    __inout_opt PVOID                pv,
    __in_opt    PTP_CALLBACK_ENVIRON pcbe
    );

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

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

VOID WaitForThreadpoolWorkCallbacks(
    __inout PTP_WORK pwk,
    __in    BOOL     fCancelPendingCallbacks
    );

不再需要一个工作项的时候,应该调用CloseThreadPoolWork。


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

首先定义这个回调函数,然后调用下面的函数通知线程池在何时调用我们的函数:

PTP_TIMER CreateThreadpoolTimer(
    __in        PTP_TIMER_CALLBACK   pfnti,
    __inout_opt PVOID                pv,
    __in_opt    PTP_CALLBACK_ENVIRON pcbe
    );

想要向线程池注册计时器的时候,调用SetThreadpoolTimer函数,它也可以用来修改已有了的计时器。

可以通过调用IsThreadpoolTimerSet来查看某个计时器是否已经被设置。

相应的,WatiForThreadpoolTimerCallbacks来让线程等待一个计时器完成,还可以调用CloseThreadpoolTimer来释放计时器的内存。


3、在内核对象触发时调用一个函数

如果想到注册一个工作项,让它在一个内核对象被触发的时候执行,那么,首先编写这个调用函数,然后调用CreateThreadpoolWait来创建一个线程池等待对象,创建完成后,调用函数SetThreadpoolWait来将一个内核对象绑定到这个线程池。

线程池在内部会让一个线程调用WaitForMultipleObjects函数,传入通过SetThreadpoolWait函数注册的一组句柄,并传FALSE给bWaitAll参数。这样任何一个句柄被触发,线程池就会被唤醒。

最后,可以通过调用WaitForThreadpoolWaitCallbacks函数来等待一个等待项完成,还可以通过调用CloseThreadpoolWait函数来释放一个等待项的内存。


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

首先,必须编写一个调用函数,当I/O操作完成的时候,这个函数会被调用并得到一个指向OVERLAPPED结构的指针,这个指针是我们在调用ReadFile或WriteFile时传入的。

然后,我们调用CreateThreadpoolIo来创建一个线程池I/O对象,并将我们想要与线程池内部的I/O完成端口相关联的文件/设备句柄当第一个参数传入,创建完毕之后,调用函数StartThreadpoolIo将嵌入在I/O项中的文件/设备与线程池内部的I/O完成端口关联起来。

在每次调用ReadFile和WriteFile之前,都必须调用StartThreadpoolIo,否则回调函数不会被调用。

如果想在发生I/O请求之后让线程池停止调用我们的回调函数,可以调用函数CancelThreadpoolIo。

当对文件/设备的使用完成后,应该调用CloseThreadpoolIo解除它与线程池的关系。

函数WaitForThreadpoolIoCallbacks用来让另一个线程等待一个等处理的I/O请求完成。


十二、纤程

应该尽量避免使用纤程,尽量对应用程序进行合理的设计,使之能够使用Windows提供的线程。纤程是为了帮助将UNIX代码移植到WINDOWS中增加的,因为UNIX服务器应用程序是单线程的,但创建了自己的线程框架函数库,可以用来模拟纯粹的线程。这个线程包能够创建多个栈,保存某些CPU寄存器,并能够在它们之间进行切换来对客户请求进行服务。

纤程是在用户模式下实现的,内核对纤程一无所知,内核会根据我们自定义的算法来对纤程进行调试。由于纤程调用算法是我们定义的,因此就内核而言,它对纤程的调度不是抢占式的、

一个线程可以包含一个或多个纤程。线程一次只能执行一个纤程的代码。

使用纤程的第一个步骤是将一个已有的线程转换为一个纤程。可以通过函数ConvertThreadToFiber来达到目的,这个函数会为纤程的执行上下文分配内存(约200字节),它返回的实际上是纤程的执行上下文的内存地址。

除非我们打算创建更多的纤程,并让它们在同一个线程中运行,否则没有理由将一个线程转换为纤程。为了创建另一个纤程,线程(即当前正在运行的那个纤程)应该调用CreateFiber,它返回的是纤程执行上下文的内存地址。但与ConvertThreadToFiber不同的是,这个新的纤程不会执行,为了让新的纤程执行,应该调用SwitchToFiber,它是让纤程得到CPU时间的唯一方法。

调用DeleteFiber来销毁纤程,用GetCurrentFiber得到当前运行纤程的执行上下文的地址。GetFiberData将创建或转换纤程时传入的参数值返回给纤程回调函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值