Linux 下多线程的方案实现与对比
2024-05-03刘坤禹王欢欢甄帅辉
王 鑫,扈 月,刘坤禹,王欢欢,甄帅辉
(1 中国电子科技集团公司第三十研究所 四川 成都 610041)
(2 中国人民解放军31012 部队 北京 100091)
0 引言
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位[1]。多线程技术主要解决任务并发的问题,它的核心在于将宏观的任务量拆解为微观的任务分量,将任务分量派发到不同的线程上,从而减少任务的处理时间。多线程使用场景一般分为两种:第一种为按需分配,即针对每个任务派生一个独立的线程去处理该任务,虽然在任务量少的应用中,该方式处理效率高,但是在任务量巨大的应用中,该方式在线程创建和销毁中将耗费大量时间;第二种为预先分配,即提前创建包含一定数目线程的线程池,线程池又分为静态线程池和动态线程池两种,静态线程池通过预先创建包含固定数量线程的线程池去处理任务,动态线程池相较于静态线程池的不同点在于可以通过任务量的变化动态调整线程池中线程的数量,它们都能节省线程的创建和销毁时间,从而降低每个线程的平均创建和销毁时间。
静态线程池预先创建一定数量线程后,线程数量不再变化,用以处理所有任务,但当任务达到波峰时,任务响应时间将会加大,降低用户体验感。动态线程池也是预先创建一定数量线程后,通过对任务量的分析,动态调整线程池中线程数量的大小,降低任务响应时间。动态线程池实现时面临一些问题,例如动态调整线程池系数不恰当和单个工作线程如何回收销毁。
本文将对以上提出的三种多任务处理模型进行分析,并着重优化设计动态线程池,通过不同规模的模拟任务,不同模型参数的调整,对比验证三种模型。
1 方案介绍
1.1 多线程方案
多线程方案即“随时请求,随时创建,随时回收”,任务请求送入任务调度模块,任务调度模块创建线程进行任务请求处理,任务处理完毕后,线程回收模块进行线程的回收和销毁,面对多少任务,即需创建多少个线程进行任务处理。
该方案逻辑简单,实现便捷,适用于小规模且无周期性的任务量。但是当任务量爆发式增长时,该方案受限于操作系统和硬件资源所能创建的线程数总量,当线程数达到上限后,新来的任务将不能得到及时处理[2]。
1.2 静态线程池方案
静态线程池方案即“预先创建,随时请求,统一回收”,初始化模块创建预先设定数量M 的线程,当出现任务请求,通过任务入队模块将任务存入任务队列中,线程池从任务队列中获取任务,进行任务处理,线程池的数量始终保持不变。方案示意如图1 所示。
图1 静态线程池方案示意图
针对一定规模且周期性的任务量,利用静态线程池的方案,可以节省线程创建和销毁的时间,加快任务处理速度,提升用户体验感。当任务量达到线程池的数量M 后,没有新的线程可以处理任务队列中的任务,会加大一些任务的处理时间,降低用户体验感。
1.3 动态线程池方案
动态线程池方案即“动态创建,随时请求,动态回收”。初始化模块创建预先设定数量的线程,当出现任务请求,通过任务入队模块将任务存入任务队列中,线程池从任务队列中获取任务,进行任务处理。当任务监控模块检测到线程池空闲线程数量在设定的时间T1 内一直为0且任务队列中的任务数量不为0,任务监控模块将根据调整系数P1 进行新线程的创建;当任务监控模块检测到线程池空闲线程数量在设定时间T2 内一直不为0 且任务队列中的任务数据为0,任务监控模块将根据调整系数P2进行空闲线程的回收。方案示意如图2 所示。
图2 动态线程池方案示意图
针对一定规模、周期性、具有井喷式特性的任务量,利用动态线程池的方案,不仅可以节省线程创建和销毁的时间,而且当任务量井喷式地增长时,可以动态调整线程数量,加快任务处理速度,提升用户体验感。当任务量经过潮汐趋于平稳时,通过销毁线程池中空闲的线程,确保线程池的活性[3]。该方案的难点主要为如何确定线程池动态调整的系数,如何高效回收空闲线程。
因多线程方案和静态线程池方案逻辑和结构简单,因此下面主要针对动态线程池进行分析与设计。
2 动态线程池方案分析与设计
传统动态线程池采用“动态创建,随时请求,动态回收”的方式对线程池进行控制,其中动态调整线程池线程数和线程池的空闲线程安全退出机制的选择对动态线程池的性能起着关键作用。本文主要针对以上两点进行分析与设计。
2.1 动态调整线程池线程数
针对动态调整线程数,传统的做法为基于预测公式进行线程数调整,该方法实现复杂,且使用场景具有局限性[4]。
固定线程数量的线程池在某些场景下不能满足应用需求,线程池常见的动态调整线程数量的方案有基于设定值触发和基于任务量趋势预测两种形式:①基于任务量趋势预测的方式对任务建模要求高,需要结合统计学原理进行任务量趋势预测,能够真实反映任务量的变化从而进行高效处理,但是该方式的使用场景具有局限性,且需要较大的算力资源;②基于设定值触发的方式通过预先设置不同规模的参数集合,通过任务量的跟踪选择合适的参数,实现简单,线程池维护开销小且通用性较高。本文为保证线程池的通用性,采用基于设定值触发方式来动态调整线程池。
(1)合理设置相关参数
若设线程池中最大线程数为T_MAX,最小线程数为_MIN,最大空闲线程数为T_FMAX,最小空闲线程数为T_FMIN,处理原则是:①在线程池初始化阶段,创建T_MIN个空闲线程。②线程池中空闲线程数量T_FNOW 低于T_FMIN 时,通过待处理任务量派生调整系数P1 触发线程池调整进行P1×T_MIN 个线程添加。③线程池中空闲线程数量T_FNOW 不低于T_FMIN 且小于T_FMAX 时,通过固定调整系数P2 触发线程池调整进行P2×(T_FNOWTFMIN)个线程删除。④线程池中空闲线程数量不低于T_FMAX 时,通过固定调整系数P3 触发线程池调整进行P2×(T_FNOW-TFMAX)个线程删除。⑤调整过程中保证调整后的线程池线程数量小于T_MAX 且大于等于T_MIN。
(2)延迟销毁线程
在任务量激增的情况下通过设定值触发增加一定数量的线程后,当任务量减少时,需要销毁这部分创建的线程。此时并不真正地销毁这些线程,而是将它放入销毁列表中,创建并使能定时器。当定时器计时完毕时,若无创建新线程的需求将会销毁这些线程;如果在定时器计时完毕前,有创建新线程的需求,将删除该销毁列表的定时器,并使用这些还未销毁的线程,根据任务量变化重复前面的步骤,从而达到了提高线程的复用。
2.2 安全退出线程
针对空闲线程安全退出,传统的做法为在线程创建初期进行统计,并对创建的线程进行状态监控,当需要减少线程数量时,通过状态监控发现空闲线程并进行回收,该方法存在的问题为对线程进行状态监控将耗费大量计算资源[5]。
线程退出的方式主要分为线程主动退出和线程被动退出两种,多线程方案和静态线程池方案不需要考虑线程退出的方式,直接进行线程回收即可。动态线程池方案需要考虑线程回收的方式,需检测线程池中的线程是否处于空闲状态。一般采用轮询或者信号触发,轮询即重复地查询某个线程的状态,将会耗费大量的计算资源;信号量方式需要维护大量的信号量,耗费大量的存储资源。
因此本文提出一种空闲自动释放机制,当任务监控模块做出决策应减少线程数量时,将“自杀”任务放入任务队列中,此时空闲线程便无差别地从任务队列中领取任务,从而释放资源,结束线程。
3 代码架构
多线程方案即一任务一线程,用完即毁,使用操作系统自带函数即可实现。
本文通过添加函数等实现了线程池创建、初始化、获取线程数量和获取线程池状态等接口,通过对该系列接口的动态组合,可以实现静态线程池和动态线程池两种方案。
(1)使用方法
(2)接口与功能
采用面向对象的思想,将线程池的方法定义在sl_thread_pool 结构体中,实现线程池的初始化、状态查询和线程数量调整等功能。
针对多线程方案,通过任务使能系统函数pthread_create 创建处理任务的线程,在该线程内首先进行系统函数pthread_detach 操作,使得该线程处理完任务后可以自动退出。
静态线程池和动态线程池代码组织方式基本一致,不同之处在于动态线程池多了线程池数量的动态调整过程。实际使用中的线程池通常与其他模块深度结合,所以线程池的接口需要尽可能独立。
4 测试对比
(1)测试环境。FT-2000 处理器(4 核,2.6 GHz),内存1 X 8 G/DDR4/2666 MHz/noECC/1.2 v, 银河麒麟4.0.2。
(2)测试设计线程池相比原来多线程的机制优势主要体现在:节省线程的创建和销毁时间以及能够快速地将线程进行复用,但需考虑使用场景;任务并发量小,池内的线程复用程度不高,能够节省的少量线程的创建时间和销毁时间,会被池内线程的维护开销所抵消;任务执行时间很长,工作线程执行任务的时间远远大于线程创建和调度的时间,那些短暂的时间不会带来明显的性能提升,因为线程池也需要进行部分维护工作。结合线程池常见的使用环境,将测试内容定向到密集但耗时少的短任务上。测试对象是线程池对任务的平均响应时间,任务派发方式为以一定时间T 为间隔,随机产生任务数N添加到任务队列直至任务数达到设定任务数M,其中以0 ~1 ms 之间的任意值作为时间间隔T,以10~50 个之间的任意值作为增加任务数N。分别与系统多线程方式和静态线程池方式进行比较,使用50 个静态线程与50 ~500 个动态线程池进行测试,测试结果如表1 所示。
表1 测试结果 ms
从上表的测试结果可以看出,当任务数越少时,处理任务的效率:多线程>静态线程池>动态线程池;当任务数达到一定数量N时,处理任务的效率:静态线程池>动态线程池>多线程;当任务数量远远超过N时,处理任务的效率:动态线程池>静态线程池>多线程。
5 结语
综上所述,本文对多线程处理并发任务进行了概述,并提出了多线程、静态线程池和动态线程池三种方案。针对动态线程池进行详细设计,提出了线程回收方法和线程池参数调整方法,通过对方案进行代码设计并进行不同数量的模拟任务进行测试。测试结果表明,在不同规模的任务量下,可以选择不同的多线程方案,达到性能优化的效果。目前绝大多数应用都存在任务需求量大,潮汐不定等特性,因此动态线程池在提高任务响应时间,提升用户体验等方面存在明显优势。