技术开发 频道

浅谈线程池:独立线程池的作用及IO线程池

 【IT168技术文档】

    所转载自Jeff Zhao的博客

    在上一篇文章中,我们简单讨论了线程池的作用,以及CLR线程池的一些特性。不过关于线程池的基本概念还没有结束,这次我们再来补充一些必要的信息,有助于我们在程序中选择合适的使用方式。

 独立线程池

 上次我们讨论到,在一个.NET应用程序中会有一个CLR线程池,可以使用ThreadPool类中的静态方法来使用这个线程池。我们只要使用QueueUserWorkItem方法向线程池中添加任务,线程池就会负责在合适的时候执行它们。我们还讨论了CLR线程池的一些高级特性,例如对线程的最大和最小数量作限制,对线程创建时间作限制以避免突发的大量任务消耗太多资源等等。

 那么.NET提供的线程池又有什么缺点呢?有些朋友说,一个重要的缺点就是功能太简单,例如只有一个队列,没法做到对多个队列作轮询,无法取消任务,无法设定任务优先级,无法限制任务执行速度等等。不过其实这些简单的功能,倒都可以通过在CLR线程池上增加一层(或者说,通过封装CLR线程池)来实现。例如,您可以让放入CLR线程池中的任务,在执行时从几个自定义任务队列中挑选一个运行,这样便达到了对多个队列作轮询的效果。因此,在我看来,CLR线程池的主要缺点并不在此。

 我认为,CLR线程池的主要问题在于“大一统”,也就是说,整个进程内部几乎所有的任务都会依赖这个线程池。如前篇文章所说的那样,如Timer和WaitForSingleObject,还有委托的异步调用,.NET框架中的许多功能都依赖这个线程池。这个做法是合适的,但是由于开发人员对于统一的线程池无法做到精确控制,因此在一些特别的需要就无法满足了。举个最常见例子:控制运算能力。什么是运算能力?那么还是从线程讲起吧1。

 我们在一个程序中创建一个线程,安排给它一个任务,便交由操作系统来调度执行。操作系统会管理系统中所有的线程,并且使用一定的方式进行调度。什么是“调度”?调度便是控制线程的状态:执行,等待等等。我们都知道,从理论上来说有多少个处理单元(如2 * 2 CPU的机器便有4个处理单元),就表示操作系统可以同时做几件事情。但是线程的数量会远远超过处理单元的数量,因此操作系统为了保证每个线程都被执行,就必须等一个线程在某个处理器上执行到某个情况的时候,“换”一个新的线程来执行,这便是所谓的“上下文切换(context switch)”。至于造成上下文切换的原因也有多种,可能是某个线程的逻辑决定的,如遇上锁,或主动进入休眠状态(调用Thread.Sleep方法),但更有可能是操作系统发现这个线程“超时”了。在操作系统中会定义一个“时间片(timeslice)”2,当发现一个线程执行时间超过这个时间,便会把它撤下,换上另外一个。这样看起来,多个线程——也就是多个任务在同时运行了。

 值得一提的是,对于Windows操作系统来说,它的调度单元是线程,这和线程究竟属于哪个进程并没有关系。举个例子,如果系统中只有两个进程,进程A有5个线程,而进程B有10个线程。在排除其他因素的情况下,进程B占有运算单元的时间便是进程A的两倍。当然,实际情况自然不会那么简单。例如不同进程会有不同的优先级,线程相对于自己所属的进程还会有个优先级;如果一个线程在许久没有执行的时候,或者这个线程刚从“锁”的等待中恢复,操作系统还会对这个线程的优先级作临时的提升——这一切都是牵涉到程序的运行状态,性能等情况的因素,有机会我们在做展开。

 现在您意识到线程数量意味着什么了没?没错,就是我们刚才提到的“运算能力”。很多时候我们可以简单的认为,在同样的环境下,一个任务使用的线程数量越多,它所获得的运算能力就比另一个线程数量较少的任务要来得多。运算能力自然就涉及到任务执行的快慢。您可以设想一下,有一个生产任务,和一个消费任务,它们使用一个队列做临时存储。在理想情况下,生产和消费的速度应该保持相同,这样可以带来最好的吞吐量。如果生产任务执行较快,则队列中便会产生堆积,反之消费任务就会不断等待,吞吐量也会下降。因此,在实现的时候,我们往往会为生产任务和消费任务分别指派独立的线程池,并且通过增加或减少线程池内线程数量来条件运算能力,使生产和消费的步调达到平衡。

 使用独立的线程池来控制运算能力的做法很常见,一个典型的案例便是SEDA架构:整个架构由多个Stage连接而成,每个Stage均由一个队列和一个独立的线程池组成,调节器会根据队列中任务的数量来调节线程池内的线程数量,最终使应用程序获得优异的并发能力。

 在Windows操作系统中,Server 2003及之前版本的API也只提供了进程内部单一的线程池,不过在Vista及Server 2008的API中,除了改进线程池的性能之外,还提供了在同一进程内创建多个线程池的接口。很可惜,.NET直到如今的4.0版本,依旧没有提供构建独立线程池的功能。构造一个优秀的线程池是一件相当困难的事情,幸运的是,如果我们需要这方面的功能,可以借助著名的SmartThreadPool,经过那么多年的考验,相信它已经足够成熟了。如果需要,我们还可以对它做一定修改——毕竟在不同情况下,我们对线程池的要求也不完全相同。

0
相关文章