11线程池的使用

11线程池的使用
11线程池的使用

第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请求完成时调用函数。

为了完成这些操作,线程池由4个独立的部分组成。表11 - 1显示了这些组件并描述了控制其行为特性的规则。

表11-1 线程池的组件及其行为特性

当进程初始化时,它并不产生与这些组件相关联的任何开销。但是,一旦新线程池函数之一被调用时,就为进程创建某些组件,并且其中有些组件将被保留,直到进程终止运行为止。如你所见,使用线程池所产生的开销并不小。相当多的线程和内部数据结构变成了你的进程的一个组成部分。因此必须认真考虑线程池能够为你做什么和不能做什么,不要盲目地使用这些函数。

好了,上述说明已经足够了。下面让我们来看一看这些函数能够做些什么。

11.1 方案1:异步调用函数

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

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

BOOL QueueUserWorkItem(

PTHREAD_START_ROUTINE pfnCallback,

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组件中的线程数量,然后根据负荷量(已排队的工作项目的数量)将另一个线程添加给该组件。接着Q u e u e U s e r Wo r k I t e m执行对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),并调用函数。当函数返回时,该线程再次调用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个工作项目进行了排队(使用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个线程,必须分开调用Q u e u

e U s e r Wo r k I t e m函数,这样某些工作项目就有机会完成运行。

线程池不能对线程池中的线程数量规定一个上限,否则就会发生渴求或死锁现象。假如有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 CreateTimerQueue();

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

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

BOOL CreateTimerQueueTimer(

PHANDLE phNewTimer,

HANDLE hTimerQueue,

WAITORTIMERCALLBACK pfnCallback,

PVOID pvContext,

DWORD dwDueTime,

DWORD dwPeriod,

ULONG dwFlags);

对于第二个参数,可以传递想要在其中创建定时器的定时器队列的句柄。如果只是创建少数几个定时器,只需要为h Ti m e r Q u e u e参数传递N U L L,并且完全避免调用C r e a t e Ti m e r Q u e u e函数。传递N U L L,会告诉该函数使用默认的定时器队列,并且简化了你的代码。p f n C a l l b a c k和p v C o n t e x t参数用于指明应该调用什么函数以及到了规定的时间应该将什么传递给该函数。d w D u e Ti m e参数用于指明应该经过多少毫秒才能第一次调用该函数(如果这个值是0,那么只要可能,就调用该函数,使得C r e a t e Ti m e r Q u e u e Ti m e r函数类似Q u

e u e U s e r Wo r k I t e m)。d w P e r i o d参数用于指明应该经过多少毫秒才能在将来调用该函数。如果为d w P e r i o d传递0,那么就使它成为一个单步定时器,使工作项目只能进行一次排队。新定时器的句柄通过函数的p h N e w Ti m e r参数返回。

工作回调函数必须采用下面的原型:

VOID WINAPI WaitOrTimerCallback(

PVOID pvContext,

BOOL fTimerOrWaitFired);

当该函数被调用时,f Ti m e r O r Wa i t F i r e d参数总是T R U E,表示该定时器已经触发。

下面介绍C r e a t e Ti m e r Q u e u e Ti m e r的d w F l a g s参数。该参数负责告诉函数,当到了规定的时间时,如何给工作项目进行排队。如果想要让非I / O组件的线程来处理工作项目,可以使用W T _ E X E C U T E D E FA U LT。如果想要在某个时间发出一个异步I / O 请求,可以使用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 P E R S I S T E N T T H R E A D。如果认为工作项目需要很长的时间来运行,可以使用W T _ E X E C U T E L O N G F U N C T I O N。

也可以使用另一个标志,即W T _ E X E C U T E I N T I M E RT H R E A D,下面将介绍它。在表11 - 1中,能够看到线程池有一个定时器组件。该组件能够创建单个定时器内核对象,并且能够管理它的到期时间。该组件总是由单个线程组成。当调用C r e a t e Ti m e r Q u e u e Ti m e r 函数时,可以使定时器组件的线程醒来,将你的定时器添加给一个定时器队列,并重置等待定时器内核对象。然后该定时器组件的线程便进入待命睡眠状态,等待该等待定时器将一个A P C放入它的队列。当等待定时器将该A P C放入队列后,线程就醒来,更新定时器队列,重置等待定时器,然后决定对现在应该运行的工作项目执行什么操作。

接着,该线程要检查下面这些标志:W T _ E X E C U T E D E FA U LT、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 I N P E R S I S T E N T T H R E A D 、W T _ E X E C U T E L O N G F U N C T I O N和W T _E X E C U T E I N T I M E RT H R E A D。不过现在可以清楚地看到W T _ E X E C U T E D I N T I M E RT H R E A D标志执行的是什么操作:它使定时器组件的线程能够执行该工作项目。虽然这使工作项目的运行效率更高,但是这非常危险。如果工作项目函数长时间中断运行,那么等待定时器的线程就无法执行任何其他操作。虽然等待定时器可能仍然将A P C项目排队放入该线程,但是在当前运行的函数返回之前,这些工作项目不会得到处理。如果打算使用定时器线程来执行代码,那么该代码应该迅速执行,不应该中断。

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 I N P E R S I S T E N T T H R E A D和W T _ E X E C U T E I N T I M E RT H R E A D等标志是互斥的。如果不传递这些标志中的任何一个(或者使用W T _ E X E C U T E D E FA U LT标志),那么工作项目就排队放入I / O组件的线程中。另外,如果设定了W T _ E X E C U T E I N T I M E RT H R E A D标志,那么W T _ E X E C U T E L O N G F U N C T I O N将被忽略。

当不再想要触发定时器时,必须通过调用下面的函数将它删除:

BOOL DeleteTimerQueueTimer(

HANDLE hTimerQueue,

HANDLE hTimer,

HANDLE hCompletionEvent);

即使对于已经触发的单步定时器,也必须调用该函数。h Ti m e r Q u e u e参数指明定时器位于哪个队列中。h Ti m e r参数指明要删除的定时器,句柄通过较早时调用C r e a t e Ti m e r Q u e u e Ti m e r来返回。

最后一个参数h C o m p l e t i o n E v e n t告诉你,由于该定时器,什么时候将不再存在没有处理的已排队的工作项目。如果为该参数传递I N VA L I D _ H A N D L E _ VA L U E,那么在该定时器的所有已排队工作项目完成运行之前, D e l e t e Ti m e r Q u e u e Ti m e r函数不会返回。请想一想这将意味着什么。如果在定时器处理自己的工作项目期间对定时器进行一次中断删除,就会造成一个死锁条件。虽然你正在等待工作项目完成处理操作,但是你在等待它完成操作时却中断了它的处理。只有当线程不是处理定时器的工作项目的线程时,该线程才能进行对定时器的中断删除。

另外,如果你正在使用定时器组件的线程,不应该试图对任何定时器进行中断删除,否则就会产生死锁。如果试图删除一个定时器,就会将一个A P C通知放入该定时器组件的线程队列中。如果该线程正在等待一个定时器被删除,而它不能删除该定时器,那么就会发生死锁。

如果不为h C o m p l e t i o n E v e n t参数传递I N VA L I D _ H A N D L E _ VA L U E,可以传递N U L L。这将告诉该函数,你想尽快删除定时器。在这种情况下, D e l e t e Ti m e r Q u e u e Ti m e r将立即返回,但是你不知道该定时器的所有工作项目何时完成处理。最后,你可以传递一个事件内核对象的句柄作为h C o m p l e t i o n E v e n t的参数。当这样操作时, D e l e t e Ti m e r Q u e u e Ti m e r将立即返回,同时,当定时器的所有已经排队的工作项目完成运行之后,定时器组件的线程将设置该事件。在调用D e l e t e Ti m e r Q u e u e Ti m e r之前,千万不要给该事件发送通知,否则你的代码将认为排队的工作项目已经完成运行,但是实际上它们并没有完成。

一旦创建了一个定时器,可以调用下面这个函数来改变它的到期时间和到期周期:

BOOL ChangeTimerQueueTimer(

HANDLE hTimerQueue,

HANDLE hTimer,

ULONG dwDueTime,

ULONG dwPeriod);

这里传递了定时器队列的句柄和想要修改的现有定时器的句柄。可以修改定时器的d w D u e Ti m e和d w P e r i o d。注意,试图修改已经触发的单步定时器是不起作用的。另外,你可以随意调用该函数,而不必担心死锁。

当不再需要一组定时器时,可以调用下面这个函数,删除定时器队列:

BOOL DeleteTimerQueueEx(

HANDLE hTimerQueue,

HANDLE hCompletionEvent);

该函数取出一个现有的定时器队列的句柄,并删除它里面的所有定时器,这样就不必为删除每个定时器而显式调用D e l e t e Ti m e r Q u e u e Ti m e r。h C o m p l e t i o n E v e n t参数在这里的语义与它在D e l e t e Ti m e r Q u e u e Ti m e r函数中的语义是相同的。这意味着它存在同样的死锁可能性,因此必须小心。

在开始介绍另一个方案之前,让我们说明两个其他的项目。首先,线程池的定时器组件创建等待定时器,这样,它就可以给A P C项目排队,而不是给对象发送通知。这意味着操作系统能够连续给A P C项目排队,并且定时器事件从来不会丢失。因此,设置一个定期定时器能够保证每个间隔时间都能为你的工作项目排队。如果创建一个定期定时器,每隔1 0 s触发一次,那么每隔1 0 s就调用你的回调函数。必须注意这在使用多线程时也会发生必须对工作项目函数的各个部分实施同步。

如果不喜欢这种行为特性,而希望你的工作项目在每个项目执行之后的1 0 s进行排队,那么应

该在工作项目函数的结尾处创建单步定时器。或者可以创建一个带有高超时值的单个定时器,并在工作项目函数的结尾处调用C h a n g e Ti m e r Q u e u e Ti m e r.

Ti m e d M s g B o x示例应用程序

清单11 - 1列出的Ti m e d M s g B o x应用程序(“11 Ti m e d M s g B o x . e x e”)显示了如何使用线程池的定时器函数来实现一个用户在规定时间内不作出响应时能自动关闭的消息框。该应用程序的源代码和资源文件位于本书所附光盘上的11 - Ti m e d M s g B o x目录下。

当启动该程序时,它将全局变量g _ n S e c L e f t设置为1 0。这表示用户必须在规定时间内对消息框作出响应的秒数。然后调用C r e a t e Ti m e r Q u e u e Ti m e r函数,指令线程池每秒钟调用一次M s g B o x Ti m e o u t函数。一旦一切都已初始化,便调用M e s s a g e B o x,并向用户显示图11 - 1所示的消息框。

在等待用户作出响应的时候,线程池中的一个线程便调用M s g B o x Ti m e o u t函数。该函数寻找消息框的窗口句柄,对全局变量g _ n S e c L e f t进行递减,并更新消息框中的字符串。当M s g B o x Ti m e o u t第一次被调用后,消息框就类似下面的样子(见图11 - 2 )。

图11-1 调用Message Box 时出现的消息框

图11-2 调用MsgBox Timeout 后出现的消息框

当M s g B o x Ti m e o u t第1 0次被调用时,g _ n S e c L e f t变量变为0,同时M s g B o x Ti m e o u t调用E n d D i a l o g函数来撤消该消息框。主线程调用的M e s s a g e B o x返回,D e l e t e Ti m e r Q u e u e Ti m e r被调用,以告诉线程池停止调用M s g B o x Ti m e o u t函数,这时出现图11 - 3所示的消息框,告诉用户他没有在分配给他的时间内对图11 - 1所示的消息框作出响应。

如果用户没有在时间到期之前作出响应,便出现图11 - 4所示的消息框。

图11-3 对图11 - 1所示的消息框不作出响应时出现的消息框

图11-4 在超时前不作出响应时出现的消息框

清单11-1 Ti m e d M s g B o x示例应用程序

/******************************************************************** **********

Module: TimedMsgBox.cpp

Notices: Copyright (c) 2000 Jeffrey Richter

********************************************************************* *********/

#include "..\CmnHdr.h" /* See Appendix A. */

#include

///////////////////////////////////////////////////////////////////// /////////

// The caption of our message box

TCHAR g_szCaption[] = TEXT("Timed Message Box");

// How many seconds we'll display the message box

int g_nSecLeft = 0;

// This is STATIC window control ID for a message box

#define ID_MSGBOX_STATIC_TEXT 0x0000ffff

///////////////////////////////////////////////////////////////////// /////////

VOID WINAPI MsgBoxTimeout(PVOID pvContext, BOOLEAN fTimeout) {

// NOTE: Due to a thread race condition, it is possible (but very unlikely)

// that the message box will not be created when we get here.

HWND hwnd = FindWindow(NULL, g_szCaption);

if (hwnd != NULL) {

// The window does exist; update the time remaining.

TCHAR sz[100];

wsprintf(sz, TEXT("You have %d seconds to respond"),

g_nSecLeft--);

SetDlgItemText(hwnd, ID_MSGBOX_STATIC_TEXT, sz);

if (g_nSecLeft == 0) {

// The time is up; force the message box to exit.

EndDialog(hwnd, IDOK);

}

} else {

// The window does not exist yet; do nothing this time.

// We'll try again in another second.

}

}

///////////////////////////////////////////////////////////////////// /////////

int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {

chWindows9xNotAllowed();

// How many seconds we'll give the user to respond

g_nSecLeft = 10;

// Create a multishot 1 second timer that begins firing after 1 second. HANDLE hTimerQTimer;

CreateTimerQueueTimer(&hTimerQTimer, NULL, MsgBoxTimeout, NULL,

1000, 1000, 0);

// Display the message box

MessageBox(NULL, TEXT("You have 10 seconds to respond"),

g_szCaption, MB_OK);

// Cancel the timer & delete the timer queue

DeleteTimerQueueTimer(NULL, hTimerQTimer, NULL);

// Let us know if the user responded or if we timed-out.

MessageBox(NULL,

(g_nSecLeft == 0) ? TEXT("Timeout") : TEXT("User responded"), TEXT("Result"), MB_OK);

return(0);

}

//////////////////////////////// End of File

/////////////////////////////////

//Microsoft Developer Studio generated resource script.

//

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS

///////////////////////////////////////////////////////////////////// ////////

//

// Generated from the TEXTINCLUDE 2 resource.

//

#include "afxres.h"

///////////////////////////////////////////////////////////////////// ////////

#undef APSTUDIO_READONLY_SYMBOLS

///////////////////////////////////////////////////////////////////// ////////

// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)

#ifdef _WIN32

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

#pragma code_page(1252)

#endif //_WIN32

///////////////////////////////////////////////////////////////////// ////////

//

// Icon

//

// Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems.

IDI_TIMEDMSGBOX ICON DISCARDABLE "TimedMsgBox.ico"

#ifdef APSTUDIO_INVOKED

///////////////////////////////////////////////////////////////////// ////////

//

// TEXTINCLUDE

//

1 TEXTINCLUDE DISCARDABLE

BEGIN

"resource.h\0"

END

2 TEXTINCLUDE DISCARDABLE

BEGIN

"#include ""afxres.h""\r\n"

"\0"

END

3 TEXTINCLUDE DISCARDABLE

BEGIN

"\r\n"

"\0"

END

#endif // APSTUDIO_INVOKED

#endif // English (U.S.) resources

///////////////////////////////////////////////////////////////////// ////////

#ifndef APSTUDIO_INVOKED

///////////////////////////////////////////////////////////////////// ////////

//

// Generated from the TEXTINCLUDE 3 resource.

//

///////////////////////////////////////////////////////////////////// ////////

#endif // not APSTUDIO_INVOKED

11.3 方案3:当单个内核对象变为已通知状态时调用函数

M i c r o s o f t发现,许多应用程序产生的线程只是为了等待内核对象变为已通知状态。一旦对象得到通知,该线程就将某种通知移植到另一个线程,然后返回,等待该对象再次被通知。有些编程人员甚至编写了代码,在这种代码中,若干个线程各自等待一个对象。这对系统资源是个很大的浪费。当然,与创建进程相比,创建线程需要的的开销要小得多,但是线程是需要资源的。每个线程有一个堆栈,并且需要大量的C P U指令来创建和撤消线程。始终都应该尽量减少它使用的资源。

如果想在内核对象得到通知时注册一个要执行的工作项目,可以使用另一个新的线程池函数:

BOOL RegisterWaitForSingleObject(

PHANDLE phNewWaitObject,

HANDLE hObject,

WAITORTIMERCALLBACK pfnCallback,

PVOID pvContext,

ULONG dwMilliseconds,

ULONG dwFlags);

该函数负责将参数传送给线程池的等待组件。你告诉该组件,当内核对象(用h O b j e c t进行标识)得到通知时,你想要对工作项目进行排队。也可以传递一个超时值,这样,即使内核对象没有变为已通知状态,也可以在规定的某个时间内对工作项目进行排队。超时值0和I N F I N I T E是合法的。一般来说,该函数的运行情况与Wa i t F o r S i n g l e O b j e c t函数(第9章已经介绍)相似。当注册了一个等待组件后,该函数返回一个句柄(通过p h N e w Wa i t O b j e c t 参数)以标识该等待组件。

在内部,等待组件使用Wa i t F o r M u l t i p l e O b j e c t s函数来等待已经注册的对象,并且要受到该函数已经存在的任何限制的约束。限制之一是它不能多次等待单个句柄。因此,如果想要多次注册单个对象,必须调用D u p l i c a t e H a n d l e函数,并对原始句柄和复制的句柄分开进行注册。当然,Wa i t F o r M u l t i p l e O b j e c t s能够等待已通知的对象中的任何一个,而不是所有的对象。如果熟悉Wa i t F o r M u l t i p l e O b j e c t s函数,那么一定知道它一次最多能够等待6 4(M A X I M U M _WA I T _ O B J E C T S)个对象。如果用R e g i s t e r Wa i t F

o r S i n g l e O b j e c t函数注册的对象超过6 4个,那么将会出现什么情况呢?这时等待组件就会添加另一个也调用Wa i t F o r M u l t i p l e O b j e c t s函数的线程。实际上,每隔6 3个对象后,就要将另一个线程添加给该组件,因为这些线程也必须等待负责控制超时的等待定时器对象。当工作项目准备执行时,它被默认排队放入非I / O组件的线程中。这些线程之一最终将会醒来,并且调用你的函数,该函数的原型必须是下面的形式:

VOID WINAPI WaitOrTimerCallbackFunc(

PVOID pvContext,

BOOLEAN fTimerOrWaitFired);

如果等待超时了,f Ti m e r O r Wa i t F i r e d参数的值是T R U E。如果等待时对象变为已通知状态,则该参数是FA L S E。

对于R e g i s t e r Wa i t F o r S i n g l e O b j e c t函数的d w F l a g s参数,可以传递W T _ E X E C U T E I N -WA I T T H R E A D,它使等待组件的线程之一运行工作项目函数本身。它的运行速率更高,因为工作项目不必排队放入I / O组件中。但是这样做有一定的危险性,因为正在执行工作项目的等待组件函数的线程无法等待其他对象得到通知。只有当工作项目函数运行得很快时,才应该使用该标志。

如果工作项目将要发出异步I / O请求,或者使用从不终止运行的线程来执行某些操作,那么也可以传递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 I N P E R S I S T E N T T H R E A D。也可以使用W T _ E X E C U T E L O N G F U N C T I O N标志来告诉线程池,你的函数可能要花费较长的时间来运行,而且它应该考虑将一个新线程添加给线程池。只有当工作项目正在被移植到非I / O组件或I / O组件中时,才能使用该标志,如果使用等待组件的线程,不应该运行长函数。

应该了解的最后一个标志是W T _ E X E C U T E O N LY O N C E。假如你注册一个等待进程内核对象的组件,一旦该进程对象变为已通知状态,它就停留在这个状态中。这会导致等待组件连续地给工作项目排队。对于进程对象来说,可能不需要这个行为特性。如果使用W T _E X E C U T E O N LY O N C E标志,就可以防止出现这种情况,该标志将告诉等待组件在工作项目执行了一次后就停止等待该对象。

现在,如果正在等待一个自动重置的事件内核对象。一旦该对象变为已通知状态,该对象就重置为它的未通知状态,并且它的工作项目将被放入队列。这时,该对象仍然处于注册状态,同时,等待组件再次等待该对象被通知,或者等待超时(它已经重置)结束。当不再想让该等待组件等待你的注册对象时,必须取消它的注册状态。即使是使用W T _ E X E C U T E O N LY O N C E 标志注册的并且已经拥有队列的工作项目的等待组件,情况也是如此。调用下面这个函数,可以取消等待组件的注册状态:

BOOL UnregisterWaitEx(

HANDLE hWaitHandle,

HANDLE hCompletionEvent);

第一个参数指明一个注册的等待(由R e g i s t e r Wa i t F o r S i n g l e O b j e c t返回),第二个参数指明当已注册的、正在等待的所有已排队的工作项目已经执行时,你希望如何通知你。与D e l e t e Ti m e r Q u e u e Ti m e r函数一样,可以传递N U L L(如果不要通知的话),或者传递I N VA L I D _ H A N D L E _ VA L U E(中断对函数的调用,直到所有排队的工作项目都已执行),也可以传递一个事件对象的句柄(当排队的工作项目已经执行时,它就会得到通知)。对于无中断的函数调用,如果没有排队的工作项目,那么U n r e g i s t e r Wa i t E x返回T R U E,否则它返回FA L S E,而G e t L a s t E r r o r返回S TAT U S _ P E N D I N G。

同样,当你将I N VA L I D _ H A N D L E _ VA L U E传递给U n r e g i s t e r Wa i t E x时,必须小心避免死锁状态。在试图取消等待组件的注册状态,从而导致工作项目运行时,该工作项目函数不应该中断自己的运行。这好像是说:暂停我的运行,直到我完成运行为止一样——这会导致死锁。然而,如果等待组件的线程运行一个工作项目,而该工作项目取消了导致工作项目运行的等待组件的注册状态,U n r e g i s t e r Wa i t E x是可以用来避免死锁的。还有一点需要说明,在取消等待组件的注册状态之前,不要关闭内核对象的句柄。这会使句柄无效,同时,等待组件的线程会在内部调用Wa i t F o r M u l t i p l e O b j e c t s函数,传递一个无效句柄。Wa i t F o r M u l t i p l e O b j e c t s的运行总是会立即失败,整个等待组件将无法正常工作。

最后,不应该调用P u l s e E v e n t函数来通知注册的事件对象。如果这样做了,等待组件的线程就可能忙于执行某些别的操作,从而错过了事件的触发。这不应该是个新问题了。P u l s e E v e n t几乎能够避免所有的线程结构产生这个问题。

11.4 方案4:当异步I/O请求完成运行时调用函数

最后一个方案是个常用的方案,即服务器应用程序发出某些异步I / O请求,当这些请求完成时,需要让一个线程池准备好来处理已完成的I / O请求。这个结构是I / O完成端口原先设计时所针对的一种结构。如果要管理自己的线程池,就要创建一个I / O完成端口,并创建一个等待该端口的线程池。还需要打开多个I / O设备,将它们的句柄与完成端口关联起来。当异步I / O请求完成时,设备驱动程序就将“工作项目”排队列入该完成端口。

这是一种非常出色的结构,它使少数线程能够有效地处理若干个工作项目,同时它又是一种很特殊的结构,因为线程池函数内置了这个结构,使你可以节省大量的设计和精力。若要利用这个结构,只需要打开设备,将它与线程池的非I / O组件关联起来。记住,I / O组件的线程全部在一个I / O组件端口上等待。若要将一个设备与该组件关联起来,可以调用下面的函数:

BOOL BindIoCompletionCallback(

HANDLE hDevice,

POVERLAPPED_COMPLETION_ROUTINE pfnCallback,

ULONG dwFlags);

该函数在内部调用C r e a t e I o C o m p l e t i o n P o r t,传递h D e v i c e和内部完成端口的句柄。调用B i n d I o C o m p l e t i o n C a l l b a c k也可以保证至少有一个线程始终在非I / O组件中。与该设备相关联的完成关键字是重叠完成例程的地址。这样,当该设备的I / O运行完成时,非I / O组件就知道要调用哪个函数,以便它能够处理已完成的I / O请求。该完成例程必须采用下面的原型:

VOID WINAPI OverlappedCompletionRoutine(

DWORD dwErrorCode,

DWORD dwNumberOfBytesTransferred,

POVERLAPPED pOverlapped);

你将会注意到没有将一个O V E R L A P P E D结构传递给B i n d I o C o m p l e t i o n C a l l b a c k。O V E R L A P P E D结构被传递给R e a d F i l e和Wr i t e F i l e之类的函数。系统在内部始终保持对这个带有待处理I / O请求的重叠结构进行跟踪。当该请求完成时,系统将该结构的地址放入完成端口,从而使它能够被传递给你的O v e r l a p p e d C o m p l e t i o n R o u t i n e 函数。另外,由于该完成例程的地址是完成的关键,因此,如果要将更多的上下文信息放入O v e r l a p p e d C o m p l e t i o n R o u t i n e函数,应该使用将上下文信息放入O V E R L A P P E D结构的结尾处的传统方法。

还应该知道,关闭设备会导致它的所有待处理的I / O请求立即完成,并产生一个错误代码。要作好准备,在你的回调函数中处理这种情况。如果关闭设备后你想确保没有运行任何回调函数,那么必须引用应用程序中的计数特性。换句话说,每次发出一个I / O请求时,必须使计数器的计数递增,每次完成一个I / O请求,则递减计数器的计数。

目前没有特殊的标志可以传递给B i n d I o C o m p l e t i o n C a l l b a c k函数的d w F l a g s参数,因此必须传递0。相信你能够传递的标志是W T _ E X E C U T E I N I O T H R E A D。如果一个I / O请求已经完成,它将被排队放入一个非I / O组件线程。在O v e r l a p p e d C o m p l e t i o n R o u t i n e函数中,可以发出另一个异步I / O请求。但是记住,如果发出I / O请求的线程终止运行,该I / O请求也会被撤消。另外,非I / O组件中的线程是根据工作量来创建或撤消的。如果工作量很小,该组件中的线程就会终止运行,其I / O 请求仍然处于未处理状态。如果B i n d I o C o m p l e t i o n C a l l b a c k 函数支持W T _ E X E C U T E I N I O T H R E A D标志,那么在完成端口上等待的线程就会醒来,并将结果移植到一个I / O组件的线程中。由于在I / O请求处于未处理状态下时这些线程决不会终止运行,因此可以发出I / O请求而不必担心它们被撤消。虽然W T _ E X E C U T E I N I O T H R E A D标志的作用不错,但是可以很容易模仿刚才介绍的行为特性。在O v e r l a p p e d C o m p l e t i o n R o u t i n e函数中,只需要调用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 I N I O T H R E A D标志和想要的任何数据(至少是重叠结构)。这就是线程池函数能够为你执行的全部功能。

JAVA线程池原理333

在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.将需处理的任务的数量大 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销 2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。 线程池工作原理:

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。 线程池的替代方案 线程池远不是服务器应用程序内使用多线程的唯一方法。如同上面所提到的,有时,为每个新任务生成一个新线程是十分明智的。然而,如果任务创建过于频繁而任务的平均处理时间过短,那么为每个任务生成一个新线程将会导致性能问题。 另一个常见的线程模型是为某一类型的任务分配一个后台线程与任务队列。AWT 和 Swing 就使用这个模型,在这个模型中有一个 GUI 事件线程,导致用户界面发生变化的所有工作都必须在该线程中执行。然而,由于只有一个 AWT 线程,因此要在 AWT 线程中执行任务可能要花费相当长时间才能完成,这是不可取的。因此,Swing 应用程序经常需要额外的工作线程,用于运行时间很长的、同 UI 有关的任务。 每个任务对应一个线程方法和单个后台线程(single-background-thread)方法在某些情形下都工作得非常理想。每个任务一个线程方法在只有少量运行时间很长的任务时工作得十分好。而只要调度可预见性不是很重要,则单个后台线程方法就工作得十分好,如低优先级后台任务就是这种情况。然而,大多数服务器应用程序都是面向处理大量的短期任务或子任务,因此往往希望具有一种能够以低开销有效地处理这些任务的机制以及一些资源管理和定时可预见性的措施。线程池提供了这些优点。 工作队列 就线程池的实际实现方式而言,术语“线程池”有些使人误解,因为线程池“明显的”实现在大多数情形下并不一定产生我们希望的结果。术语“线程池”先于Java 平台出现,因此它可能是较少面向对象方法的产物。然而,该术语仍继续广泛应用着。 虽然我们可以轻易地实现一个线程池类,其中客户机类等待一个可用线程、将任务传递给该线程以便执行、然后在任务完成时将线程归还给池,但这种方法却存在几个潜在的负面影响。例如在池为空时,会发生什么呢?试图向池线程传递任务的调用者都会发现池为空,在调用者等待一个可用的池线程时,它的线程将阻塞。我们之所以要使用后台线程的原因之一常常是为了防止正在提交的线程被阻塞。完全堵住调用者,如在线程池的“明显的”实现的情况,可以杜绝我们试图解决的问题的发生。 我们通常想要的是同一组固定的工作线程相结合的工作队列,它使用 wait() 和

解决多线程中11个常见问题

并发危险 解决多线程代码中的11 个常见的问题 Joe Duffy 本文将介绍以下内容:?基本并发概念 ?并发问题和抑制措施 ?实现安全性的模式?横切概念本文使用了以下技术: 多线程、.NET Framework 目录 数据争用 忘记同步 粒度错误 读写撕裂 无锁定重新排序 重新进入 死锁 锁保护 戳记 两步舞曲 优先级反转 实现安全性的模式 不变性 纯度 隔离 并发现象无处不在。服务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。 与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。 这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。 这些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就开始执行并发操作,则不会遇到这种情况。本

文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。 首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。 当从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework 之类的程序)基本上都基于共享内存概念,进程中的所有线程均可访问驻留在同一虚拟地址空间中的数据。静态变量和堆分配可用于共享。请考虑下面这个典型的例子: static class Counter { internal static int s_curr = 0; internal static int GetNext() { return s_curr++; } } Counter 的目标可能是想为GetNext 的每个调用分发一个新的唯一数字。但是,如果程序中的两个线程同时调用GetNext,则这两个线程可能被赋予相同的数字。原因是s_curr++ 编译包括三个独立的步骤: 1.将当前值从共享的s_curr 变量读入处理器寄存器。 2.递增该寄存器。 3.将寄存器值重新写入共享s_curr 变量。 按照这种顺序执行的两个线程可能会在本地从s_curr 读取了相同的值(比如42)并将其递增到某个值(比如43),然后发布相同的结果值。这样一来,GetNext 将为这两个线程返回相同的数字,导致算法中断。虽然简单语句s_curr++ 看似不可分割,但实际却并非如此。 忘记同步 这是最简单的一种数据争用情况:同步被完全遗忘。这种争用很少有良性的情况,也就是说虽然它们是正确的,但大部分都是因为这种正确性的根基存在问题。 这种问题通常不是很明显。例如,某个对象可能是某个大型复杂对象图表的一部分,而该图表恰好可使用静态变量访问,或在创建新线程或将工作排入线程池时通过将某个对象作为闭包的一部分进行传递可变为共享图表。 当对象(图表)从私有变为共享时,一定要多加注意。这称为发布,在后面的隔离上下文中会对此加以讨论。反之称为私有化,即对象(图表)再次从共享变为私有。 对这种问题的解决方案是添加正确的同步。在计数器示例中,我可以使用简单的联锁: static class Counter { internal static volatile int s_curr = 0; internal static int GetNext() { return Interlocked.Increment(ref s_curr);

java深入理解线程池

深入研究线程池 一.什么是线程池? 线程池就是以一个或多个线程[循环执行]多个应用逻辑的线程集合. 注意这里用了线程集合的概念是我生造的,目的是为了区分执行一批应用逻辑的多个线程和 线程组的区别.关于线程组的概念请参阅基础部分. 一般而言,线程池有以下几个部分: 1.完成主要任务的一个或多个线程. 2.用于调度管理的管理线程. 3.要求执行的任务队列. 那么如果一个线程循环执行一段代码是否是线程池? 如果极端而言,应该算,但实际上循环代码应该算上一个逻辑单元.我们说最最弱化的线程池 应该是循环执行多个逻辑单元.也就是有一批要执行的任务,这些任务被独立为多个不同的执行单元.比如: int x = 0; while(true){ x ++; } 这就不能说循环中执行多个逻辑单元,因为它只是简单地对循环外部的初始变量执行++操作. 而如果已经有一个队列 ArrayList al = new ArrayList(); for(int i=0;i<10000;i++){ al.add(new AClass()); } 然后在一个线程中执行: while(al.size() != 0){ AClass a = (AClass)al.remove(0); a.businessMethod(); } 我们说这个线程就是循环执行多个逻辑单元.可以说这个线程是弱化的线程池.我们习惯上把这些相对独立的逻辑单元称为任务. 二.为什么要创建线程池? 线程池属于对象池.所有对象池都具有一个非常重要的共性,就是为了最大程度复用对象.那么 线程池的最重要的特征也就是最大程度利用线程. 从编程模型模型上说讲,在处理多任务时,每个任务一个线程是非常好的模型.如果确实可以这么做我们将可以使用编程模型更清楚,更优化.但是在实际应用中,每个任务一个线程会使用系统限入"过度切换"和"过度开销"的泥潭. 打个比方,如果可能,生活中每个人一辆房车,上面有休息,娱乐,餐饮等生活措施.而且道路交道永远不堵车,那是多么美好的梦中王国啊.可是残酷的现实告诉我们,那是不可能的.不仅每个人一辆车需要无数多的社会资源,而且地球上所能容纳的车辆总数是有限制的. 首先,创建线程本身需要额外(相对于执行任务而必须的资源)的开销.

完成端口加线程池技术实现

WinSock 异步I/O模型[5]---完成端口 - Completion Port ---------------------------------------------------------------------------- 如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了五种I/O模型,分别是: ■选择(select); ■异步选择(WSAAsyncSelect); ■事件选择(WSAEventSelect); ■重叠I/O(Overlapped I/O); ■完成端口(Completion Port) 。 每一种模型适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确,综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。 ============================================== █“完成端口”模型是迄今为止最复杂的一种 I/O 模型。但是,若一个应用程序同时需要管理很多的套接字, 那么采用这种模型,往往可以达到最佳的系统性能!但缺点是,该模型只适用于Windows NT 和 Windows 2000 以上版本的操作系统。 █因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多, 应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。 █从本质上说,完成端口模型要求我们创建一个 Win32 完成端口对象,通过指定数量的线程, 对重叠 I/O 请求进行管理,以便为已经完成的重叠 I/O 请求提供服务。

使用 GPars 解决常见并发问题

重庆IT论坛https://www.360docs.net/doc/3016754244.html, 在并发性时代,带有4、6 和16 个处理器核心的芯片变得很普遍,而且在不久的将来,我们会看到带有上百甚至上千个核心的芯片。这种处理能力蕴含着巨大的可能性,但对于软件开发人员来说,它也带来了挑战。最大限度地利用这些闪耀新核的需求推动了对并发性、状态管理和为两者构建的编程语言的关注热潮。 Groovy、Scala 和Clojure 等JVM 语言满足了这些需求。这三种都是较新的语言,运行于高度优化的JVM 之上,可以使用Java 1.5 中新增的强大的Java 并发库。尽管每种语言基于其原理采用不同的方法,不过它们都积极支持并发编程。 在本文中,我们将使用GPars,一种基于Groovy 的并发库,来检查模型以便解决并发性问题,比如后台处理、并行处理、状态管理和线程协调。 为何选择Groovy ?为何选择GPars ? Groovy 是运行于JVM 之上的一种动态语言。基于Java 语言,Groovy 移除了Java 代码中的大量正式语法,并添加了来自其他编程语言的有用特性。Groovy 的强大特性之一是它允许编程人员轻松创建基于Groovy 的DSL。(一个DSL 或域特定语言是一种旨在解决特定编程问题的脚本语言。参阅参考资料了解有关DSL 的更多信息。) 获取代码和工具 参阅参考资料部分下载Groovy、GPars 和本文中用到的其他工具。您可以随时下载本文的可执行代码样例。 GPars 或Groovy Parallel Systems 是一种Groovy 并发库,捕捉并发性和协调模型作为DSL。GPars 的构思源自其他语言的一些最受欢迎的并发性和协调模型,包括:?来自Java 语言的executors 和fork/join ?来自Erlang 和Scala 的actors ?来自Clojure 的agents ?来自Oz 的数据流变量 Groovy 和GPars 的结合成为展示各种并发性方法的理想之选。甚至不熟悉Groovy 的Java 开发人员也能轻松关注相关讨论,因为Groovy 的语法以Java 语言为基础。本文中的示例基于Groovy 1.7 和GPars 0.10。 回页首 后台和并行处理 一个常见的性能难题是需要等待I/O。I/O 可能涉及到从一个磁盘、一个web 服务或甚至是一名用户读取数据。当一个线程在等待I/O 的过程中被阻止时,将等待中的线程与原始执行线程分离开来将会很有用,这将使它能继续工作。由于这种等待是在后台发生的,所以我们称这种技术为后台处理。 例如,假设我们需要这样一个程序,即调用Twitter API 来找到针对若干JVM 语言的最新tweets 并将它们打印出来。Groovy 能够使用Java 库twitter4j 很容易就编写出这样的程序,如清单1 所示: 清单1. 串行读取tweets (langTweets.groovy) import twitter4j.Twitter import twitter4j.Query

Tomcat线程池实现简介

Tomcat线程池实现简介 目前市场上常用的开源Java Web容器有Tomcat、Resin和Jetty。其中Resin从V3.0后需要购买才能用于商业目的,而其他两种则是纯开源的。可以分别从他们的网站上下载最新的二进制包和源代码。 作为Web容器,需要承受较高的访问量,能够同时响应不同用户的请求,能够在恶劣环境下保持较高的稳定性和健壮性。在HTTP服务器领域,Apache HTTPD的效率是最高的,也是最为稳定的,但它只能处理静态页面的请求,如果需要支持动态页面请求,则必须安装相应的插件,比如mod_perl可以处理Perl脚本,mod_python可以处理Python脚本。 上面介绍的三中Web容器,都是使用Java编写的HTTP服务器,当然他们都可以嵌到Apache 中使用,也可以独立使用。分析它们处理客户请求的方法有助于了解Java多线程和线程池的实现方法,为设计强大的多线程服务器打好基础。 Tomcat是使用最广的Java Web容器,功能强大,可扩展性强。最新版本的Tomcat(5.5.17)为了提高响应速度和效率,使用了Apache Portable Runtime(APR)作为最底层,使用了APR 中包含Socket、缓冲池等多种技术,性能也提高了。APR也是Apache HTTPD的最底层。可想而知,同属于ASF(Apache Software Foundation)中的成员,互补互用的情况还是很多的,虽然使用了不同的开发语言。 Tomcat 的线程池位于tomcat-util.jar文件中,包含了两种线程池方案。方案一:使用APR 的Pool技术,使用了JNI;方案二:使用Java实现的ThreadPool。这里介绍的是第二种。如果想了解APR的Pool技术,可以查看APR的源代码。 ThreadPool默认创建了5个线程,保存在一个200维的线程数组中,创建时就启动了这些线程,当然在没有请求时,它们都处理“等待”状态(其实就是一个while循环,不停的等待notify)。如果有请求时,空闲线程会被唤醒执行用户的请求。 具体的请求过程是:服务启动时,创建一个一维线程数组(maxThread=200个),并创建空闲线程(minSpareThreads=5个)随时等待用户请求。当有用户请求时,调用threadpool.runIt(ThreadPoolRunnable)方法,将一个需要执行的实例传给ThreadPool中。其中用户需要执行的实例必须实现ThreadPoolRunnable接口。ThreadPool 首先查找空闲的线程,如果有则用它运行要执行ThreadPoolRunnable;如果没有空闲线程并且没有超过maxThreads,就一次性创建minSpareThreads个空闲线程;如果已经超过了maxThreads了,就等待空闲线程了。总之,要找到空闲的线程,以便用它执行实例。找到后,将该线程从线程数组中移走。接着唤醒已经找到的空闲线程,用它运行执行实例(ThreadPoolRunnable)。运行完ThreadPoolRunnable后,就将该线程重新放到线程数组中,作为空闲线程供后续使用。 由此可以看出,Tomcat的线程池实现是比较简单的,ThreadPool.java也只有840行代码。用一个一维数组保存空闲的线程,每次以一个较小步伐(5个)创建空闲线程并放到线程池中。使用时从数组中移走空闲的线程,用完后,再“归还”给线程池。ThreadPool提供的仅仅是线程池的实现,而如何使用线程池也是有很大学问的。让我们看看Tomcat是如何使用ThreadPool的吧。 Tomcat有两种EndPoint,分别是AprEndpoint和PoolTcpEndpoint。前者自己实现了一套线

多线程编程的详细说明完整版

VB .NET多线程编程的详细说明 作者:陶刚整理:https://www.360docs.net/doc/3016754244.html, 更新时间:2011-4-1 介绍 传统的Visual Basic开发人员已经建立了同步应用程序,在这些程序中事务按顺序执行。尽管由于多个事务多多少少地同时运行使多线程应用程序效率更高,但是使用先前版本的Visual Basic很难建立这类程序。 多线程程序是可行的,因为操作系统是多任务的,它有模拟同一时刻运行多个应用程序的能力。尽管多数个人计算机只有一个处理器,但是现在的操作系统还是通过在多个执行代码片断之间划分处理器时间提供了多任务。线程可能是整个应用程序,但通常是应用程序可以单独运行的一个部分。操作系统根据线程的优先级和离最近运行的时间长短给每一个线程分配处理时间。多线程对于时间密集型事务(例如文件输入输出)应用程序的性能有很大的提高。 但是也有必须细心的地方。尽管多线程能提高性能,但是每个线程还是需要用附加的内存来建立和处理器时间来运行,建立太多的线程可能降低应用程序的性能。当设计多线程应用程序时,应该比较性能与开销。 多任务成为操作系统的一部分已经很久了。但是直到最近Visual Basic程序员才能使用无文档记录特性(undocumented)或者间接使用COM组件或者操作系统的异步部分执行多线程事务。.NET框架组件为开发多线程应用程序,在System.Threading名字空间中提供了全面的支持。 本文讨论多线程的好处以及怎样使用Visual Basic .NET开发多线程应用程序。尽管Visual Basic .NET和.NET框架组件使开发多线程应用程序更容易,但是本文作了调整使其适合高级读者和希望从早期Visual Basic转移到Visual Basic .NET的开发人员。 多线程处理的优点 尽管同步应用程序易于开发,但是它们的性能通常比多线程应用程序低,因为一个新的事务必须等待前面的事务完成后才能开始。如果完成某个同步事务的时间比预想的要长,应用程序可能没有响应。多线程处理可以同时运行多个过程。例如,字处理程序能够在继续操作文档的同时执行拼写检查事务。因为多线程应用程序把程序分解为独立的事务,它们能通过下面的途径充分提高性能: l 多线程技术可以使程序更容易响应,因为在其它工作继续时用户界面可以保持激活。 l 当前不忙的事务可以把处理器时间让给其它事务。 l 花费大量处理时间的事务可以周期性的把时间让给其它的事务。 l 事务可以在任何时候停止。 l 可以通过把单独事务的优先级调高或调低来优化性能。 明确地建立多线程应用程序的决定依赖于几个因素。多线程最适合下面的情况:

11线程池的使用

第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请求完成时调用函数。 为了完成这些操作,线程池由4个独立的部分组成。表11 - 1显示了这些组件并描述了控制其行为特性的规则。 表11-1 线程池的组件及其行为特性

Java定时任务ScheduledThreadPoolExecutor

Timer计时器有管理任务延迟执行("如1000ms后执行任务")以及周期性执行("如每500ms执行一次该任务")。但是,Timer存在一些缺陷,因此你应该考虑使用ScheduledThreadPoolExecutor作为代替品,Timer对调度的支持是基于绝对时间,而不是相对时间的,由此任务对系统时钟的改变是敏感的;ScheduledThreadExecutor只支持相对时间。 Timer的另一个问题在于,如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。Timer线程并不捕获异常,所以TimerTask抛出的未检查的异常会终止timer 线程。这种情况下,Timer也不会再重新恢复线程的执行了;它错误的认为整个Timer都被取消了。此时,已经被安排但尚未执行的TimerTask永远不会再执行了,新的任务也不能被调度了。 例子: packagecom.concurrent.basic; importjava.util.Timer; import java.util.TimerTask; public class TimerTest { private Timer timer = new Timer(); // 启动计时器 public void lanuchTimer() { timer.schedule(new TimerTask() { public void run() { throw new RuntimeException(); } }, 1000 * 3, 500); } // 向计时器添加一个任务 public void addOneTask() { timer.schedule(new TimerTask() { public void run() { System.out.println("hello world"); } }, 1000 * 1, 1000 * 5); }

线程池原理 C++实现

线程池原理及创建(C++实现) 时间:2010‐02‐25 14:40:43来源:网络 作者:未知 点击:2963次 本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关。另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后,我们给出一个 本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关。另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后,我们给出一个简单示例程序,通过该示例程序,我们会发现,通过该线程池框架执行多线程任务是多么的简单。 为什么需要线程池 目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。 传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。 我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。 T1:线程创建时间 T2:线程执行时间,包括线程的同步等时间 T3:线程销毁时间

那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%‐50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。 除此之外,线程池能够减少创建的线程个数。通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。 因此线程池的出现正是着眼于减少线程池本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。 基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销。 构建线程池框架 一般线程池都必须具备下面几个组成部分: 线程池管理器:用于创建并管理线程池 工作线程: 线程池中实际执行的线程

tcl多线程编程的使用说明

321C H A P T E R II. Advanced Tcl 21Multi-Threaded Tcl Scripts This chapter describes the Thread extension for creating multi-threaded Tcl scripts. This Chapter is from Practical Programming in Tcl and Tk, 4th Ed. Copyright 2003 ? Brent Welch, Ken Jones https://www.360docs.net/doc/3016754244.html,/book/ T hread support, a key feature of many languages, is a recent addition to Tcl. That’s because the Tcl event loop supports features implemented by threads in most other languages, such as graphical user interface management, multi-client servers, asynchronous communication,and scheduling and timing operations. However, although Tcl’s event loop can replace the need for threads in many circumstances, there are still some instances where threads can be a better solution: ?Long-running calculations or other processing, which can “starve” the event loop ?Interaction with external libraries or processes that don’t support asynchro- nous communication ?Parallel processing that doesn’t adapt well to an event-driven model ?Embedding Tcl into an existing multi-threaded application What are Threads? Traditionally , processes have been limited in that they can do only one thing at a time. If your application needed to perform multiple tasks in parallel, you designed the application to create multiple processes. However, this approach has its drawbacks. One is that processes are relatively “heavy” in terms of the resources they consume and the time it takes to create them. For applications that frequently create new processes — for example, servers that create a new

100行Java代码构建一个线程池

100行Java代码构建一个线程池 本示例程序由三个类构成,第一个是TestThreadPool类,它是一个测试程序,用来模拟客户端的请求,当你运行它时,系统首先会显示线程池的初始化信息,然后提示你从键盘上输入字符串,并按下回车键,这时你会发现屏幕上显示信息,告诉你某个线程正在处理你的请求,如果你快速地输入一行行字符串,那么你会发现线程池中不断有线程被唤醒,来处理你的请求,在本例中,我创建了一个拥有10个线程的线程池,如果线程池中没有可用线程了,系统会提示你相应的警告信息,但如果你稍等片刻,那你会发现屏幕上会陆陆续续提示有线程进入了睡眠状态,这时你又可以发送新的请求了。 第二个类是ThreadPoolManager类,顾名思义,它是一个用于管理线程池的类,它的主要职责是初始化线程池,并为客户端的请求分配不同的线程来进行处理,如果线程池满了,它会对你发出警告信息。 最后一个类是SimpleThread类,它是Thread类的一个子类,它才真正对客户端的请求进行处理,SimpleThread在示例程序初始化时都处于睡眠状态,但如果它接受到了ThreadPoolManager类发过来的调度信息,则会将自己唤醒,并对请求进行处理。 首先我们来看一下TestThreadPool类的源码: //TestThreadPool.java 1 import java.io.*; 2 3 4 public class TestThreadPool 5 { 6 public static void main(String[] args) 7 { 8 try{ 9 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 10 String s; 11 ThreadPoolManager manager = new ThreadPoolManager(10); 12 while((s = br.readLine()) != null) 13 {

TASK使用总结

Task类使用总结 2012-12-27 20:51:19 标签:C#多线程Task并行 原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本声明。否则将追究法律责任。 https://www.360docs.net/doc/3016754244.html,/2359144/1102476 由于Framework 4.0和Framework 4.5对Task类稍微有些不同,此处声明以下代码都是基于Framework 4.5 Task类和Task类,后者是前者的泛型版本。TResult类型为Task所调用方法的返回值。 主要区别在于Task构造函数接受的参数是Action委托,而Task接受的是Func委托。 1.Task(Action) 2.Task(Func) 启动一个任务 1.static void Main(string[] args) 2.{ 3.Task Task1 = new Task(() => Conso le.WriteLine("Task1")); 4.Task1.Start(); 5.Console.ReadLine(); 6.} 通过实例化一个Task对象,然后Start,这种方式中规中矩,但是实践中,通常采用更方便快捷的方式 Task.Run(() => Console.WriteLine("Foo")); 这种方式直接运行了Task,不像上面的方法还需要调用Start(); Task.Run方法是Task类中的静态方法,接受的参数是委托。返回值是为该Task 对象。 Task.Run(Action) Task.Run(Func>)

java,高并发解决方案

java,高并发解决方案 篇一:Java高并发程序设计 Java 高并发程序设计 第一课前沿 ? 在计算密集型的领域中非常适用并行程序 ? 多核CPU的产生促使了并行程序的设计,摩尔定律的失效,芯片的性能不能提高,4GHz已经是当下最高的频率,从而产生多核CPU的出现。 ? 活锁:两个资源A、B,进程1和2 分别占用A和B 时发现不能工作,就同时释放掉,然后又同时分别占用B 和A 如此反复!出现活锁 ? 阻塞: 第二课Java并行程序基础 ? 线程是比进程更细粒度的执行单元,可以说是属于进程的一个子集 ? 7, Thread 类的run()方法实现于Runnable 接口,如果直接调用run()方法,不会启动新的线程,而是在当前线程下运

行,start() 方法会开启一个新的线程。 两种方式,1 重载run方法2,实现Ruanable接口 8, Thread.Interrupt() 是一种比较的让线程终止的方法优雅的方法,不会立刻停止掉该程序,根据程序的设计,而是根据该程序的设计,当执行完该程序的一次循环之后,在下次循环程序开始之前中断,保证该程序的数据完整性。9,try { Thread.sleep(2000); } catch (InterruptedException e) { // //设置中断状态,抛出异常后会清除中断标记位e.printStackTrace(); } 10,查看当前被挂起的进程命令jps 查看进程中具体线程的信息jstack 5880 11, ? 等待线程结束join(),其本质是判断线程是否还存活,如果一直存活那么通 知调用该线程的其他线程等待,如果结束有JVM 调用notifyAll()唤醒所 有等待该线程的线程。 f (millis == 0) { while (isAlive()) { wait(0); }

大数据处理之Java线程池使用

大数据处理之Java线程池使用 前言:最近在做分布式海量数据处理项目,使用到了java的线程池,所以搜集了一些资料对它的使用做了一下总结和探究, 前面介绍的东西大多都是从网上搜集整理而来。文中最核心的东西在于后面两节无界队列线程池和有界队列线程池的实例使用以及线上问题处理方案。 1. 为什么要用线程池? 在Java中,如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,每个请求创建新线程的服务器 在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在实际处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个JVM中创建太多的线程,可能会导致系统由于过度消耗内存或者“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是“池化资源”技术产生的原因。 线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以立即请求服务,使应用程序响应更快。另外,通过适当的调整线程池中的线程数据可以防止出现资源不足的情况。 网上找来的这段话,清晰的描述了为什么要使用线程池,使用线程池有哪些好处。工程项目中使用线程池的场景比比皆是。 本文关注的重点是如何在实战中来使用好线程池这一技术,来满足海量数据大并发用户请求的场景。 2. ThreadPoolExecutor类 Java中的线程池技术主要用的是ThreadPoolExecutor 这个类。先来看这个类的构造函数, ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,

JAVA集中常用的线程池比较

1. 为什么使用线程池 诸如Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。请求以某种方式到达服务器,这种方式可能是通过网络协议(例如HTTP、FTP 或POP)、通过JMS 队列或者可能通过轮询数据库。不管请求如何到达,服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。 构建服务器应用程序的一个简单模型是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务。实际上对于原型开发这种方法工作得很好,但如果试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显。每个请求对应一个线程(thread-per-request)方法的不足之一是:为每个请求创建一个新线程的开销很大;为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。 除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。在一个JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。 线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。 2. 使用线程池的风险 虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。 2.1 死锁 任何多线程应用程序都有死锁风险。当一组进程或线程中的每一个都在等待一个只有该组中另一个进程才能引起的事件时,我们就说这组进程或线程死锁了。死锁的最简单情形是:线程 A 持有对象X 的独占锁,并且在等待对象Y 的锁,而线程 B 持有对象Y 的独占锁,却在等待对象X 的锁。除非有某种方法来打破对锁的等待(Java 锁定不支持这种方法),否则死锁的线程将永远等下去。 虽然任何多线程程序中都有死锁的风险,但线程池却引入了另一种死锁可能,在那种情况下,所有池线程都在执行已阻塞的等待队列中另一任务的执行结果的任务,但这一任务却因为没有未被占用的线程而不能运行。当线程池被用来实现涉及许多交互对象的模拟,被模拟的对象可以相互发送查询,这些查询接下来作为排队的任务执行,查询对象又同步等待着响应时,会发生这种情况。 2.2 资源不足 线程池的一个优点在于:相对于其它替代调度机制(有些我们已经讨论过)而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。线程消耗包括内存和其它系统资源在内的大量资源。除 了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会

C#线程、多线程、线程池、后台线程

C#线程、多线程、线程池、后台线程 System.Threading 命名空间 System.Threading 命名空间提供一些使得可以进行多线程编程的类和接口。除同步线程活动和数据访问的类(Monitor、Interlocked、AutoResetEvent 等)之外,此命名空间还包含一个ThreadPool 类(它使用户能够使用系统提供的线程池)和一个Timer 类(在线程池线程上执行回调方法)。除了System.Threading 命名空间中提供的功能之外,BackgroundWorker 类还提供一个简单的基于事件的方法,以同步对主应用程序线程的访问。 Thread 类 一个进程可以创建一个或多个线程以执行与该进程关联的部分程序代码。托管线程的执行单位是方法。使用ThreadStart 委托或ParameterizedThreadStart 委托可以指定由线程执行的方法。使用ParameterizedThreadStart 委托可以将数据作为参数传递到线程过程。 您不需要自己创建线程。BackgroundWorker 和ThreadPool 类使您可以通过一种面向任务的简单方式来使用系统管理的后台线程。对于将结果返回用户界面(UI) 线程的后台任务,最简单的编程方法是使用BackgroundWorker 类。下表列出了有关各种并发编程的一些信息来源。 任务请参见 执行使用事件来与主应用程序线程通信的 BackgroundWorker 后台任务。 托管线程池 执行不需要与主应用程序线程通信的后台 任务。 保护代码或字段区域,使其不被并发访问。Monitor;Visual Basic SyncLock语句(在C# 中 为lock语句) 同步多个线程的活动。EventWaitHandle、AutoResetEvent 和 ManualResetEvent 按固定的时间间隔在后台执行代码。Timer 按固定的时间间隔在UI 线程上执行代 DispatcherTimer 码。 提供对数据的无锁并发访问。互锁操作 创建您自己的线程。Thread类;启动时创建线程并传递数据 ThreadPool 类 提供一个线程池,该线程池可用于发送工作项、处理异步I/O、代表其他线程等待以及处理计时器。线程池通过为应用程序提供一个由系统管理的辅助线程池,使您可以更为有效地使用线程。一个线程监视排到线程池的若干个等待操作的状态。当一个等待操作完成时,线程池中的一个辅助线程就会执行对应的每个进程都有一个线程池。线程池的默认大小为:每个处理器250 个辅助线程,再加上1000 个I/O 完成线程。 BackgroundWorker 类

相关文档
最新文档