未加星标

Windows核心编程 第十一章 线程池的使用

字体大小 | |
[系统(windows) 所属分类 系统(windows) | 发布者 店小二03 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏
windows核心编程 第十一章 线程池的使用

4小时前来源:CSDN博客

第11章 线程池的使用

第8章讲述了如何使用让线程保持用户方式的机制来实现线程同步的方法。用户方式的同步机制的出色之处在于它的同步速度很快。如果关心线程的运行速度,那么应该了解一下用户方式的同步机制是否适用。

到目前为止,已经知道创建多线程应用程序是非常困难的。需要会面临两个大问题。一个是要对线程的创建和撤消进行管理,另一个是要对线程对资源的访问实施同步。为了对资源访问实施同步,Wi n d o w s提供了许多基本要素来帮助进行操作,如事件、信标、互斥对象和关键代码段等。这些基本要素的使用都非常方便。为了使操作变得更加方便,唯一的方法是让系统能够自动保护共享资源。不幸的是,在 Wi n d o w s提供一种让人满意的保护方法之前,我们已经有了一种这样的方法。

M i c r o s o f t公司的Windows 2000提供了一些新的线程池函数,使得线程的创建、撤消和基本管理变得更加容易。这个新的通用线程池并不完全适合每一种环境,但是它常常可以适合你的需要,并且能够节省大量的程序开发时间。

新的线程池函数使你能够执行下列操作:

异步调用函数。

按照规定的时间间隔调用函数。

当单个内核对象变为已通知状态时调用函数。

当异步I / O请求完成时调用函数。

11.1 方案1:异步调用函数

假设有一个服务器进程,该进程有一个主线程,正在等待客户机的请求。当主线程收到该请求时,它就产生一个专门的线程,以便处理该请求。这使得应用程序的主线程循环运行,并等待另一个客户机的请求。这个方案是客户机 /服务器应用程序的典型实现方法。虽然它的实现方法非常明确,但是也可以使用新线程池函数来实现它。

当服务器进程的主线程收到客户机的请求时,它可以调用下面这个函数:

BOOL QueueUserWorkItem(

PTHREAD_START_ROUTINEpfnCallback,

PVOID pvContext,

ULONG dwFlags

);

该函数将一个“工作项目”排队放入线程池中的一个线程中并且立即返回。所谓工作项目是指一个(用 p f n C a l l b a c k参数标识的)函数,它被调用并传递单个参数 p v C o n t e x t。最后,线程池中的某个线程将处理该工作项目,导致函数被调用。所编的回调函数必须采用下面的原型:

DWORD WINAPI WorkItemFunc(PVOID pvContext);

尽管必须使这个函数的原型返回D W O R D,但是它的返回值实际上被忽略了。

注意,你自己从来不调用C r e a t e T h r e a d。系统会自动为你的进程创建一个线程池,线程池中的一个线程将调用你的函数。另外,当该线程处理完客户机的请求之后,该线程并不立即被撤消。它要返回线程池,这样它就可以准备处理已经排队的任何其他工作项目。你的应用程序的运行效率可能会变得更高,因为不必为每个客户机请求创建和撤消线程。另外,由于线程与完成端口相关联,因此可以同时运行的线程数量限制为 C P U数量的两倍。这就减少了线程的上下文转移的开销。

该函数的内部运行情况是,Q u e u e U s e r Wo r k I t e m检查非I / O组件中的线程数量,然后根据负荷量(已排队的工作项目的数量)将另一个线程添加给该组件。接着 执行对P o s t Q u e u e d C o m p l e t i o n S t a t u s的等价调用,将工作项目的信息传递给 I / O完成端口。最后,在完成端口上等待的线程取出信息(通过调用 G e t Q u e u e d C o m p l e t i o n S t a t u s) ,并调用函数。当函数返回时,该线程再次调用,以便等待另一个工作项目。

线程池希望经常处理异步 I / O请求,即每当线程将一个 I / O请求排队放入设备驱动程序时,便要处理异步I / O请求。当设备驱动程序执行该I / O时,请求排队的线程并没有中断运行,而是继续执行其他指令。异步 I / O是创建高性能可伸缩的应用程序的秘诀,因为它允许单个线程处理来自不同客户机的请求。该线程不必顺序处理这些请求,也不必在等待 I / O请求运行结束时中断运行。

但是,Wi n d o w s对异步I / O请求规定了一个限制,即如果线程将一个异步 I / O请求发送给设备驱动程序,然后终止运行,那么该 I / O请求就会丢失,并且在I / O请求运行结束时,没有线程得到这个通知。在设计良好的线程池中,线程的数量可以根据客户机的需要而增减。因此,如果线程发出一个异步 I / O请求,然后因为线程池缩小而终止运行,那么该 I / O请求也会被撤消。因为这种情况实际上并不是你想要的,所以你需要一个解决方案。

如果你想要给发出异步I / O请求的工作项目排队,不能将该工作项目插入线程池的非 I / O组件中。必须将该工作项目放入线程池的 I / O组件中进行排队。该I / O组件由一组线程组成,如果这组线程还有尚未处理的 I / O请求,那么它们决不能终止运行。因此你只能将它们用来运行发出异步I / O请求的代码。

若要为I / O组件的工作项目进行排队,仍然必须调用Q u e u e U s e r Wo r k I t e m函数,但是可以为d w F l a g s参数传递W T _ E X E C U T E I N I O T H R E A D。通常只需传递W T _ E X E C U T E D E FA U LT(定义为0) ,这使得工作项目可以放入非I / O组件的线程中。

Wi n d o w s提供的函数(如R e g N o t i f y C h a n g e K e y Va l u e)能够异步执行与非 I / O相关的任务。这些函数也要求调用线程不能终止运行。如果想使用永久线程池的线程来调用这些函数中的一个,可以使用W T _ E X E C U T E I N P E R S I S T E N T T H R E A D标志,它使定时器组件的线程能够执行已排队的工作项目回调函数。由于定时器组件的线程决不会终止运行,因此可以确保最终发生异步操作。应该保证回调函数不会中断,并且保证它能迅速执行,这样,定时器组件的线程就不会受到不利的影响。

设计良好的线程池也必须设法保证线程始终都能处理各个请求。如果线程池包含 4个线程,并且有1 0 0个工作项目已经排队,每次只能处理4个工作项目。如果一个工作项目只需要几个毫秒来运行,那么这是不成问题的。但是,如果工作项目需要运行长得多的时间,那么将无法及时处理这些请求。

当然,系统无法很好地预料工作项目函数将要进行什么操作,但是,如果知道工作项目需要花费很长的时间来运行,那么可以调用 Q u e u e U s e r Wo r k I t e m函数,为它传递W T _ E X E C U T E L O N G F U N C T I O N标志。该标志能够帮助线程池决定是否要将新线程添加给线程池。如果线程池中的所有线程都处于繁忙状态,它就会强制线程池创建一个新线程。因此,如果同时对10 000个工作项目进行了排队(使用标志) ,那么这10 000个线程就被添加给该线程池。如果不想创建 10 000个线程,必须分开调用函数,这样某些工作项目就有机会完成运行。

线程池不能对线程池中的线程数量规定一个上限,否则就会发生渴求或死锁现象。假如有1 00 0 0个排队的工作项目,当第10 001个项目通知一个事件时,这些工作项目将全部中断运行。如果你已经设置的最大数量为10 000个线程,第10 001个工作项目没有被执行,那么所有的10 000个线程将永远被中断运行。

当使用线程池函数时,应该查找潜在的死锁条件。当然,如果工作项目函数在关键代码段、信标和互斥对象上中断运行,那么必须十分小心,因为这更有可能产生死锁现象。始终都应该了解哪个组件(I / O、非I / O、等待或定时器等)的线程正在运行你的代码。另外,如果工作项目函数位于可能被动态卸载的D L L中,也要小心。调用已卸载的D L L中的函数的线程将会产生违规访问。若要确保不卸载带有已经排队的工作项目的 D L L,必须对已排队工作项目进行引用计数,在调用Q u e u e U s e r Wo r k I t e m函数之前递增计数器的值,当工作项目函数完成运行时则递减该计数器的值。只有当引用计数降为0时,才能安全地卸载D L L。

11.2 方案2:按规定的时间间隔调用函数

有时应用程序需要在某些时间执行操作任务。 Wi n d o w s提供了一个等待定时器内核对象,因此可以方便地获得基于时间的通知。许多程序员为应用程序执行的每个基于时间的操作任务创建了一个等待定时器对象,但是这是不必要的,会浪费系统资源。相反,可以创建一个等待定时器,将它设置为下一个预定运行的时间,然后为下一个时间重置定时器,如此类推。然而,要编写这样的代码非常困难,不过可以让新线程池函数对此进行管理。

若要调度在某个时间运行的工作项目,首先要调用下面的函数,创建一个定时器队列:

HANDLE CreateTimeQueue;

定时器队列对一组定时器进行组织安排。例如,有一个可执行文件控制着若干个服务程序。每个服务程序需要触发定时器,以帮助保持它的状态,比如客户机何时不再作出响应,何时收集和更新某些统计信息等。让每个服务程序占用一个等待定时器和专用线程,这是不经济的。相反,每个服务程序可以拥有它自己的定时器队列(这是个轻便的资源),并且共享定时器组件的线程和等待定时器对象。当一个服务程序终止运行时,它只需要删除它的定时器队列即可,因为这会删除该队列创建的所有定时器。

一旦拥有一个定时器队列,就可以在该队列中创建下面的定时器:


Windows核心编程 第十一章 线程池的使用

本文系统(windows)相关术语:三级网络技术 计算机三级网络技术 网络技术基础 计算机网络技术

分页:12
转载请注明
本文标题:Windows核心编程 第十一章 线程池的使用
本站链接:http://www.codesec.net/view/483915.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 系统(windows) | 评论(0) | 阅读(30)