分享
 
 
 

利用C++语言设计可扩展线程池

王朝c/c++·作者佚名  2008-06-01
窄屏简体版  字體: |||超大  

摘要:在各种业务解决方案的设计中,服务器处理任务的效率是衡量方案优劣的一个重要标准。使用多线程技术并发处理任务是提高服务器效率的一个主要手段。但是频繁的线程创建、销毁和任务的分配也会降低系统效率。本文设计了一个通用的线程池,根据不同服务器所处理的任务的特点,可以设置对应的线程池参数,最大幅度的提高系统性能。

要害字:线程池多线程任务虚函数异常

概述

在各种业务解决方案的设计过程中,服务器处理任务的效率往往决定了方案的成败。多线程处理任务是提高服务器效率的主要手段,它提高了对服务器资源的利用,使得任务可以并发处理。但假如服务器处理的任务的特点是轻量级、频率高,那么线程的创建与销毁会非常频繁,而系统用于处理线程的创建与销毁的开销会占相当大的比重,反而降低了系统的效率。通过线程池技术,可以减少频繁的线程的创建与销毁对系统性能的影响。

线程池是预先创建线程的一种技术。线程池在还没有任务到来之前,创建一定数量(N1)的线程,放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。

通用线程缓冲池的设计,不仅要实现上述功能,还要考虑此设计的可移植性,减少重复开发。设计中需要考虑的重点是:

任务对象的通用性;

线程创建和销毁策略;

任务的分配策略。 分析与设计

1、任务对象的通用性

不同的业务解决方案有各自独特的任务处理方法,任务的划分上也就千差万别。为了使得在处理任务对象的时候达到一定程度的通用性,任务对象的设计上必须与实际任务的处理逻辑完全无关。从任务执行的角度看,任务不过是处理流程的一次或者多次执行的过程,可以这样来定义任务接口:

class Task

{

public:

Task();

virtual ~Task();

virtual bool run() = 0;

};

Task类是所有任务类的基类,其中的纯虚函数run()是任务流程的入口,工作线程在处理任务的时候就从此处开始执行任务的处理流程。设计一个新的任务时,只需要继续Task接口,新的任务就可以放入线程池中执行。

任务的创建、执行和销毁这样来设计:

(1)任务在其需要的时候才创建。任务的创建通过new操作,动态创建具体的任务对象,然后传入线程池,由线程池自动分配线程来执行此任务。

(2)任务是否执行完毕由其自身来决定。一个未知任务什么时候执行完毕是不可能猜测的,必须任务本身来决定。这个策略通过,Task::run()的返回值来实现。当工作线程执行一次任务时,假如返回值为true,表示任务执行完毕,就用delete操作销毁此任务;假如返回值为false,表示任务需要执行的工作并未完成,继续执行此任务。

这样的策略,使得在设计新的任务处理流程的时候,不需要过多的关心任务的接口规范,只需要在新任务类的构造函数中初始化各种资源,在新任务类的析构函数中回收资源,在run()方法中实现主要的处理逻辑,那么新的任务类即可在线程池中执行。

2、线程的创建与销毁

线程缓冲池中的维持的线程数量应该按照任务处理的需求来定。

在缓冲池刚刚建立时,线程池中有一定数量(N1)的已创建好的线程,这样可以使得新任务可以及时的得到执行。比如,某客户端在向服务器发送登陆请求的时候,这样一个请求使得服务器通常需要创建好几个相互有关联的任务。也就是说,客户端与服务器端的一次交互,通常会产生一定数量的任务。根据一个服务器所处理的业务,估计出平均情况下,一次业务产生的任务数量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控制块进行异常捕捉。虽然异常会对程序速度有很略微的影响,但是因为要执行的任务是未知的,不能保证任务可以正常执行。因为一个任务的异常而导致服务器的服务程序崩溃,这是绝对不答应的。使用异常捕捉不仅可以保证服务器流程的顺利执行,而且把异常信息存入日志文件,还可以跟踪错误。

性能测试

为了检验此线程池的性能是否和预期相同,并且分析出线程池的不同参数配置对系统性能的影响,特编写了测试程序对三组参数进行了测试,测试结果如图1所示:

横坐标是任务数量;纵坐标是消耗时间,以秒(s)为单位。

参数1:N2 = 1, N5 = 1; 参数2:N2 = 5, N5 = 1; 参数3:N2 = 5, N5 = 5

测试中,系统的总的线程数限制为500,任务都是5ms。这里只针对N2和N5进行测试,N2是平均情况下系统每次向线程池中增加的任务数量,N5是每个线程一次执行任务数量。

在任务量比较小的情况下,三者的对系统性能的占用基本上相等。但是当任务量很巨大的时候,参数1比参数2效率要稍微高出一些,而参数3的执行效率几乎是前两者的一倍。

因为都是轻量级任务,所以N2的变化对系统效率的影响并不大,而N5的影响就很显著。

结束语

通过测试可以看出,在服务器中使用线程池后,并不意味着系统性能就一定可以提升。不同系统的任务有着各自不同的特点,这就需要根据服务器任务的特点进一步调整缓冲池的一些要害参数,才能最大程度的提高系统效率。这些参数就是上面分析过程中的N1、N2、N3、N4、N5、n1、n2、n3。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有