Huihoo.org - Open Source Middleware Foundation

Last Modified: 2003.05.28

RTThreadPool设计分析


(by huihoo.org Cocia Lin(cocia@163.com))

概述

实时系统中的线程池,需要考虑的特性,主要有以下几点:
1.初始服务线程数量,动态增加线程数量
2.预分配线程池占用内存空间
3.请求缓存,缓存请求按照优先级排队
4.预估线程常用优先级,配备这些优先级的独立优先级线程池。也就是配置通道(Lane)的线程池
5.线程池销毁,释放资源

实时线程池的这些特性,重点围绕在线程资源控制,优先级控制。

在通用的非实时系统中,Doug Lea的util.conconrrenct同步线程开发包定义了很优美的结构模型和提供了基本的线程池实现。util.conconrrenct相关的内容,请自行参考。

从非实时的角度来看,Doug Lea提供的线程池实现,已经很完善。但是对于实时系统中,他缺少主要的实时特性。所以我的想法是,在非实时线程池上进行扩展,使他具备实时特性。

非实时线程池中不用修改,就可以被实时线程池利用的功能有以下几点:
1.初始服务线程数量,动态增加线程数量
2.请求缓存排队

对非实时线程池不具备的实时特性,有以下几点:
1.请求缓存按照优先级排队
2.指定任务的执行的线程优先级
3.配置通道(Lane)的线程池
4.预分配线程池占用内存空间(暂时不讨论这一点)

针对在非实时线程池之上扩展实时线程池,我们现在来一一讨论。

请求缓存按照优先级排队

我们先来看看在非实时线程池中,请求的处理:



上图中,如果队列中因为线程池没有足够的就绪线程,就需要等待,将请求暂时放置在请求队列中。而这时,可能有很多请求不断添加到请求队列中,队列中的请求任务,也是不分优先级,按照入队的顺序排列。当线程池中有空闲的线程时,在队首的第一个请求任务会首先得到运行,而不是队列中的优先级最高的任务。这就出现了优先级低的任务比优先级高的任务抢先得到运行的情况,叫做优先级翻转。这种情况,需要在实时系统中尽量避免的。

解决的方法,就是在等待运行的请求队列中,实现根据优先级排队的队列。当一个请求任务被放入队列中时,如果发现,在他之前,有低的优先级任务存在,那么,高优先级任务就会向前移动,直到他前面的优先级都没有他低为止。



Util.conconrrent同步工具包中,队列的抽象叫做Channel。并且提供了几种队列实现,但是其中没有排序队列,这就需要我们自己来实现PriorityChannel。实现代码请见参考。

有了优先级排序的队列,取代非实时线程池中的请求队列,就实现了请求缓存按照优先级排队。

指定任务的执行的线程优先级

非实时线程池中的线程,都是默认优先级的,而且都是一样优先级的。所以在任务被执行的时候,cpu时间的抢占,内存资源,网络资源等,都是按照这个默认优先级分配的。无法指定任务运行时的优先级,是无法满足实时要求的。看一下下面的代码:

用户:
PooledExecutor pool = new PooledExecutor(20);
pool.execute(new Runnable() {
public void run() {
process(connection);
}});

线程池内部:
while ( (task = getTaskFromQueue()) != null) {
 task.run();
 task = null;
}

在pool.execute()方法被调用时,将指定的任务放入请求等待队列,准备运行。然后,线程池的监护线程监测到有新任务到达,就会从请求队列中取出任务,运行。但是,任务是以运行任务的线程优先级运行的。这样就没有体现任何的优先级分别。

现在,需要把任务放入请求队列时的优先级纪录下来,任务在运行的时候,根据这个优先级,改变运行的优先级级别,任务运行完成后,再还原线程优先级。

while ( (task = getTask()) != null) {
 //Change current thread priority to the priority of task.
 RTRunnable rttask = (RTRunnable)task;
 int oldprio = setCurrentThreadPriority(rttask.getPriority());
 //run the task.
 task.run();
 task = null;
 rttask = null;
 //priority change back.
setCurrentThreadPriority(oldprio);
}

这样,线程池就可以根据请求任务的线程优先级,执行请求任务了。

配置通道(Lane)的线程池

线程池通道(Thread Lane)的概念,出自RT-CORBA规范。但是,这样的线程池形式,对所有的实时系统都很有用。

线程池通道设计的出发点是:在线程池中,分成许多小的线程池,这些小的线程池,都指定了相应的优先级,当一个请求到达的时候,按照请求的优先级,直接将请求交给对应的那个小的线程池中运行

看看下面的例子:



当有一个优先级为15的请求任务,要求被执行,就会根据优先级匹配,将这个任务交给Lane2(15)来运行。

如果有一个优先级为13的请求任务到达后,怎么处理呢?线程池会根据优先级最接近的原则,找到Lane2(15),然后取出一个就绪线程,将此线程优先级调整到请求任务要求的优先级,然后运行。

对于用户来说,用户看到得是这个大的线程池,内部的Lane对用户并不可见。当Lane2(15)中已经没有多余的线程了,而其他小的线程池中还有线程。这时用户的请求任务会被阻塞。为了优化线程池,允许线程借调行为。当当前Lane中没有多余线程可用,可以从比他优先级低的Lane中借取线程,升高借来的线程的优先级,运行请求任务。运行完成后,还原线程优先级,退还线程。当所有被他低的Lane中都没有可用线程,这时用户的请求任务才会被阻塞。

以上描述了带通道的线程池的行为方式。如果将这个线程池退化到只有一个Lane的时候,这个线程池就等价于不带Lane的线程池。所以,只要实现带通道的线程池,我们通过对Lane的设置,就可以得到带Lane的和不带Lane的线程池。实际上,我们也是这样做的。

将前面我们改造过的线程池,作为我们线程池中的Lane,再根据用户的请求任务,为请求任务匹配合适的Lane,再把任务交给具体的Lane运行,就可以了。

结束

上面,对实时线程池的分析和实现进行了讨论。具体的实现代码可以参见后面参考。上面讨论的内容,关于内存的分配没有涉及,因为这可能涉及到更多的内容。所以现阶段先不考虑这个问题。

对于上面的内容,希望大家能够交流和讨论。以资进步。

参考

开放企业基金会
http://www.huihoo.org

orbas是开放源码的一个Java CORBA的实现,对RT-CORBA进行支持。
本文描述的实时线程吃,包含在orbas源码中。
http://www.huihoo.org/orbas/index.html

Doug Lea的util.conconrrenct同步线程开发包
http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html

Real-Time Java Expert Group
http://www.rtj.org

基于Jrate的rtsj RT-ORB:ZEN
http://www.zen.uci.edu/