要创建好几个相互有关联的任务
。也就是说
,客户端与
服务器端的一次交互
,通常会产生一定数量的任务
。根据一个
服务器所处理的业务,估计出平均情况下,一次业务产生的任务数量N2。那么N1应该是N2的整数倍,N1=N2×n1,减少由于线程不够而再创建线程的概率,才能使得服务器在业务处理初期最为高效。
在线程缓冲池中的所有线程都处于繁忙状态的时候,线程池就会创建新的线程,设创建N3个。由以上分析,为了减少由于线程不够而再创建线程的概率,N3也应该是N2的整数倍,N3=N2×n2。
当服务器业务减少,出现大量线程闲置的情况,就应该销毁一部分线程。很显然,这里应该使用超时策略,当某些线程在超过时间T仍然处于闲置状态,就销毁一部分空闲线程。设销毁N4个空闲线程,为了减少由于线程不够而再创建线程的概率,N4也应该是N2的整数倍,N4=N2×n3。当然,为了使得新任务及时得到处理,即使服务器一直处于空闲,也应该保留N1个线程。
3、任务分配策略
在业务处理中,会有各种各样的任务对象,这些业务对象对系统资源的使用也不同。这些任务,无论其
空间复杂度如何,从线程执行任务这一角度来看,应该关心的主要是时间复杂度。
线程缓冲池在接收到新任务的时候,首先要寻找空闲线程,传入新任务,然后执行任务,最后还要删除任务,置空闲线程的标志。寻找空闲线程、传入任务、最后的清理工作,这些都是为了执行任务而产生的额外开销,如果所执行的任务大多数都是轻量级任务,那么额外开销带来的资源浪费就显得很突出了。为了解决这个问题,可以给一个线程传入N5个轻量级任务,这一个线程依次执行N5个轻量级任务,由于都是在很短时间内完成,并不影响任务响应的及时性。显然,N5≥1。
实现
由于源代码的篇幅关系,并不能把所有代码一一列举,这里以伪代码的形式给出线程缓冲池在线程的创建、销毁、任务分配以及任务执行方面的流程。
(1) 线程池任务分配主循环(也是一个线程)
这里除了任务分配算法外也包括了部分线程的创建与销毁的算法。
for(;;) {
pThread = GetIdleThread();// 检查空闲线程队列
if( pThread != NULL ) {
if( CheckNewTask() ) {// 有新任务
TaskList tl;
GetTask( tl ); // 取得一定数量的任务
AddTaskToThread( pTask, tl );// 把任务传入线程
continue; // 继续循环
}
}
if( pThread == NULL && nThread < THREAD_MAX )// 没有空闲线程了
CreateNewThread();// 创建新线程
continue;// 继续循环
}
// 没有要处理的任务或者已经到达线程数的上限,进入超时等待
if( WaitForTaskOrThreadTimeout() ) {
if( IncrIdleTime() > IDLE_MAX ) { // 系统空闲,计时
// 系统长时间处于空闲,销毁一定数量的空闲线程
DecrIdleThread();
}
}
else
return 0;// 线程终止
}
(2) 工作线程的任务执行流程
for(;;) {
// 检查任务队列是否有任务要运行
if( !CheckTaskQueue() ) { // 队列中没有任务
pPool->OnTaskIdle( this ); // 通知线程池,此线程已经空闲
if( WaitForTask() )
continue;// 继续循环
else
return 0;// 终止线程
} else { // 有任务需要运行
pTask = GetTask(); // 取得新任务
try {
while( !pTask->Run() ) {
// 此处循环体为空,不断运行直到任务执行完毕
}
}
catch( … ) {
WriteLog( … ); // 执行任务时产生异常,记录入日志
}
delete pTask; // 任务执行完毕,删除此任务
}
}
在任务执行的核心部分,使用了try-catch控制块进行异常捕获。虽然异常会对程序速度有很略微的影响,但是因为要执行的任务是未知的,不能保证任务可以正常执行。因为一个任务的异常而导致服务器的服务程序崩溃,这是绝对不允许的。使用异常捕获不仅可以保证服务器流程的顺利执行,而且把异常信息存入日志文件,还可以跟踪错误。