线程池学习(二)线程池详解

线程池概述

一、线程池概念

在处理大量并发任务的时候,如果按照传统的方式,来一个任务请求,创建一个线程来进行任务的处理,大量线程的创建和销毁,将消耗过多的系统资源,还增加了线程上下文(运行环境)切换的开销,而通过线程池技术就可以很好地解决这些问题。

线程池技术通过在系统中预先创建一定数量的线程,当任务请求到来时从线程池中分配一个预先创建的线程去处理任务,线程在处理完任务之后并不会销毁而是把线程归到线程池中,继续为后续的任务提供服务。

线程池的特点:

  1. 线程复用:线程池会在内部维护一定数量的线程,并在需要时重复使用这些线程来执行任务,避免频繁地创建和销毁线程,从而提高性能和效率。

  2. 控制并发性:对于多线程型,由于多线程被分配到多个处理器中,提高并行处理效率。线程池可以控制并发执行的线程数量,通过设定线程池的大小来限制系统中同时执行的线程数量,避免资源的过度占用和线程竞争导致的性能下降。

  3. 任务排队:当线程池中的线程已经全部被占用时,新提交的任务会被放入一个任务队列中进行排队等待执行。排队机制可以根据具体线程池的实现,选择不同的队列类型,如有界队列或无界队列。

  4. 提高任务的响应速度:当任务到达时,不需要等待线程创建就能立即执行任务;

  5. 提供线程管理和监控:线程池提供了易于管理和监控线程的方式,可以设置线程的属性、监控线程的状态、统计任务执行情况等。

  6. 避免资源的过度消耗:线程池可以通过限制线程数量和排队机制来避免过度消耗系统资源。当系统负载过高时,线程池可以根据配置策略进行线程数量的自动调整,以保持系统的稳定性和性能。

  7. 提供任务执行的灵活性:线程池可以接受不同类型的任务,并对这些任务进行执行调度。它提供了一系列提交任务的方法,如 execute()、submit(),同时还支持定时执行和周期性执行任务的能力。

涉及知识点:

  1. 熟悉多线程理论:多线程基本知识,线程同步,线程互斥,原子操作等;

  2. 熟悉C++11并发支持库,如:thread,mutex,condition_variable,atomic,unique_lock等;

  3. 熟悉C++11的完美转发,lambda表达式;

  4. 熟悉C++11智能指针的使用;

  5. 熟悉C++STL容器库的使用;

开发环境:

window:vs2019

Linux:g++ 要求g++版本能够支持C++11以上

按应用场景分类

1. FixedThreadPool:

固定线程池:线程池中的线程数量固定,这些线程会一直存在,不会随任务的增加或减少而动态调整,超出的任务会在队列中等待。

使用场景任务量比较固定但耗时较长的任务。

2. CachedThreadPool:

缓存线程池:可根据需要创建新线程的线程池,如果新任务到达,但线程池中没有可用线程,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。线程池中超过60秒未使用的线程,将会被移除和销毁。

使用场景:任务量大但耗时少的任务

3. SingleThreadPool:

单线程池,使用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

单线程池 ≠ 普通单线程!核心差在「池化价值」

  • 彻底规避「线程创建 / 销毁的开销」(最核心价值)
  • 实现「任务提交」和「任务执行」的解耦(工程化核心)
  • 自带「任务排队 + 统一管控」能力(扩展性拉满)
  • 极致的「线程安全」(单线程池独有的王牌优势)

使用场景:多个任务顺序执行(FIFO,优先级)。

4. WorkStealingPool:

工作窃取线程池,创建一个拥有多个任务队列(以便减少连接数)的线程池;
使用场景:会创建一个含有足够多线程的线程池,来维持相应的执行级别,它会通过工作窃取的方式,使得多线程的 CPU 不会闲置,总会有活着的线程让 CPU 去运行。如果当前工作线程处理完自己本地任务队列中的任务时,就会全全局队列或者其他工程线程的队列里面查找工作任务,帮助它们完成。利用Work Staling,可以更好实现负载均衡。

5. ScheduledThreadPool

计划线程池(定时线程池,调度线程池)
使用场景:定时以及周期性执行任务。

线程池模式

线程池模式一般分为两种:L/F领导者与跟随者模式,HS/HA半同步/半异步模式。

领导者跟随者模式讲解及分析(高并发框架)

在线程池中的线程可以在3种状态之一:领导者leader、追随者follower或工作者processor。

任何时刻线程池只有一个领导者线程。事件到达时,领导者线程负责消息分离。并从处于追随者线程中选出一个来当继任领导者,然后将自身设置为工作者状态去处置该事件,处理完毕后工作者线程将自身的状态置为追随者。在ACEP中,提供了领导者跟随者模式实现。

分析领导者跟随者

  1. 领导者(Leader)全场唯一 ,线程池里永远只有 1 个领导;核心工作是「专职监听事件 」(比如监听网络连接、监听任务队列是否有新任务),不干活、只侦察
  2. 工作者(Worker) : 数量不限;核心工作是「专职处理事件 / 执行任务」,拿到任务后埋头干活,干完活自动变回「追随者」。
  3. 追随者(Follower) : 线程池里剩余的所有线程;核心工作是「空闲待命、等待上位」,啥也不干,就等领导位置空缺,立刻竞争上岗。

关键前提:线程池启动后,会自动选举出第一个领导者,其余线程全是追随者

完整工作流程(核心就是「换领导」,共 4 步)

初始状态

部门里 10 个员工 → 1 个员工坐在「前台工位」当领导(专职盯着门口,看有没有客户来),剩下 9 个员工全在办公区「摸鱼待命」(追随者),啥也不干。

第 1 步:领导发现事件,主动「退位 + 变身干活的」

领导在前台看到客户上门(监听到事件 / 任务)→ 立刻从「前台工位」站起来 : 放弃「领导身份」,主动变成「工作者」; 带着客户需求,走到办公区工位,埋头处理需求(执行任务) 。 此时:部门暂时没有领导,前台工位空了!

第 2 步:追随者竞争上岗,诞生新领导

前台工位一空 → 办公区 9 个「追随者」员工立刻竞争 (无锁竞争,极快),最快的那个员工胜出:立刻跑到前台工位坐下,变身「新领导」;接手「监听事件」的工作,继续盯着门口有没有新客户。 此时:部门重新有了 1 个领导,剩下 8 个员工变回追随者,待命。

第 3 步:工作者干完活,变回追随者

之前的老领导(现在的工作者),干完客户需求后 → 主动回到办公区,变回「追随者」,等待下一次机会(要么竞争领导、要么等事件变身工作者)。

第 4 步:循环往复,周而复始

新领导继续监听事件 → 发现新需求,再退位变身工作者 → 追随者再竞争新领导...... 全程循环,无间断

问题:处理事件的时候要把领导者改变成工作者 再从追随者选一个当领导?意义?为啥不直接找工作者工作?

让线程池在「事件处理 + 线程调度」时,实现「零锁竞争、零调度开销、CPU 利用率最大化」,是高性能 IO / 任务调度场景下的极致优化方案,比「直接指派工作者」的常规模式快一个量级!

存在 2 个致命问题:

问题1:存在「锁竞争 + 调度开销」,性能暴跌

领导指派工作者,需要「管理追随者队列」:要加锁、要遍历队列、要通知指定线程 ------ 这个过程会产生内核态锁竞争、线程调度开销,在「每秒百万级事件」的高性能场景下,这一点点开销会被无限放大,直接拖垮系统。

问题2:「事件监听」会中断,响应延迟飙升

领导在「指派工作者」的过程中,必须暂停前台的「事件监听」工作------ 这段时间如果有新客户上门,没人接待,会导致事件响应延迟,甚至丢失事件。

再看:「领导变干活的 + 选新领导」的设计

意义 1:「事件监听永不中断」,极致低延迟(最核心)

这是该模式的第一设计目标

核心逻辑:前台工位(监听岗位)的空置时间,被压缩到极致(纳秒级)

领导发现事件→立刻起身干活,追随者立刻竞争上岗,整个过程没有任何人干预 ,前台几乎不会空岗,事件监听全程不中断,新事件能被立刻发现,响应延迟降到最低。

意义 2:全程「无锁、无调度开销」,CPU 利用率 100%

这是该模式的性能核心,也是比常规线程池快的关键!

整个流程中,没有任何锁机制、没有任何线程调度指令

领导退位、变身工作者:是线程主动行为,无需任何外部干预;

追随者竞争领导:是CPU 级别的无锁竞争(基于原子操作),比加锁快 10 倍以上;

全程没有「指派、通知、唤醒」等操作,所有线程的身份切换都是自发完成 。→ 最终效果:CPU 完全不用处理「调度、锁竞争」的杂事,所有算力都用在「监听事件 + 执行任务」上,利用率拉满。

意义 3:线程「身份复用」,无冗余线程开销

线程池里的所有线程,没有固定身份,可动态切换:领导能变干活的,干活的能变追随者,追随者能变领导。

优势:无需为「监听」和「执行」分别创建线程池,一个线程池就能搞定所有工作,避免线程数量过多导致的上下文切换开销;

对比:常规模式需要「监听线程池 + 工作线程池」两个池,线程数量多,切换开销大。

问题总结:

放弃固定的领导,让「监听」和「执行」的角色动态流转,本质是用「线程身份的灵活切换」,换取「无锁、无中断、无调度开销」的极致性能

不是「不能直接找工作者」,而是「直接找」的开销太大,在高性能场景下无法接受;

「领导变干活的」是为了立刻处理事件 ,「选新领导」是为了保证监听不中断,二者缺一不可。

领导者跟随者跟普通线程池区别

核心目标不同

  • 普通线程池:追求「任务执行的工程化」(复用、解耦、管控),兼顾性能和易用性;
  • 领导者 / 追随者池:追求「事件处理的极致性能」(低延迟、高并发、无锁),性能第一,易用性第二。

工作模式不同

  • 普通线程池:「任务队列 + 线程取任务」,线程被动等待任务,需要队列调度;
  • 领导者 / 追随者池:「主动监听 + 身份切换」,线程主动发现任务,无队列调度开销。

适用场景不同

  • 用普通线程池:业务开发、常规任务调度、IO 密集型 / CPU 密集型通用场景(99% 的业务开发用这个就够);
  • 用领导者 / 追随者池:高性能网络服务器、百万级并发 IO、实时事件处理(只有极致性能要求的底层框架才用)。

半同步/半异步模式(生产者消费者模式)讲解及分析

是比较常见的实现方式,比较简单。分为同步层、队列层、异步层三层。同步层的主线程处理工作任务并存入工作队列,工作线程从工作队列取出任务进行处理,如果工作队列为空,则取不到任务的工作线程进入挂起状态。由于线程间有数据通信,因此不适于大数据量交换的场合。

半同步/半异步模式分析

  1. 同步服务层,它处理来自上层的任务请求,上层的请求可能是并发的,这些请求不是马上就会被处理,而是将这些任务放到一个同步排队层中,等待处理。

  2. 同步排队层,来自上层的任务请求都会加到排队层中等待处理。

  3. 异步服务层,这一层中会有多个线程同时处理排队层中的任务,异步服务层从同步排队层中取出任务并存的处理。

这种三层的结构可以最大程度处理上层的并发请求。对于上层来说只要将任务丢到同步队列中就行了,至于谁去处理,什么时候处理都不用关心,主线程也不会阻塞,还能继续发起新的请求。至于任务具体怎么处理,这些细节都是靠异步服务层的多线程异步并存来完成,这些线程是一开始就创建的,不会因为大量的任务到来而创建新的线程,避免了频繁创建和销毁线程导致的系统开销,而且通过多核处理能大幅提高处理效率。

线程池实现的关键技术分析

上一节介绍了线程池的基本概念和基本结构,它是由三层组成:同步服务层、排队层和异步服务层其中排队层属于核心地位,因为上层会将任务加到排队层中,异步服务层同时也会取出任务,这里有一个同步的过程。在实现时,排队层就是一个同步队列,允许多个线程同时去添加或取出任务,并且要保证操作过程是安全的。

线程池有两个活动过程,一个是往同步队列中添加任务的过程,另一个是从同步队列中取任务的过程。

半同步半异步线程池活动图

从活动图中可以看到线程池的活动过程,一开始线程池会启动一定数量的线程,这些线程属于异步层,主要用来并行处理排队层中的任务。如果排队层中的任务数为空,则这些线程等待任务的到来。如果发现排队层中有任务了,线程池则会从等待的这些线程中唤醒一个来处理新任务。

同步服务层则会不断地将新的任务添加到同步排队中,这里有个问题值得注意,有可能上层的任务非常多,而任务又是非常耗时的,这时,异步层中的线程处理不过来,则同步排队层中的任务会不断增加,如果同步排队层不加上限控制,则可能会导致排队层中的任务过多,内存暴涨的问题。

因此,排队层需要加上限的控制,当排队层中的任务数达到上限时,就不让上层的任务添加进来,起到限制和保护的作用。

相关推荐
w-w0w-w21 小时前
C++泛型编程
开发语言·c++·算法
-西门吹雪21 小时前
C++线程之内存模型
c++
梵尔纳多21 小时前
绘制一个三角形
c++·图形渲染·opengl
汉克老师1 天前
GESP2025年12月认证C++六级真题与解析(单选题8-15)
c++·算法·二叉树·动态规划·哈夫曼编码·gesp6级·gesp六级
郝学胜-神的一滴1 天前
线程同步:并行世界的秩序守护者
java·linux·开发语言·c++·程序人生
im_AMBER1 天前
Leetcode 95 分割链表
数据结构·c++·笔记·学习·算法·leetcode·链表
明洞日记1 天前
【VTK手册032】vtkImageConstantPad:医学图像边界填充与尺寸对齐
c++·图像处理·vtk·图形渲染
Aevget1 天前
MFC扩展库BCGControlBar Pro v37.1亮点:Ribbon Bar组件全新升级
c++·ribbon·mfc·bcg·界面控件·ui开发
cchjyq1 天前
嵌入式按键调参:简洁接口轻松调参(ADC FLASH 按键 屏幕参数显示)
c语言·c++·单片机·mcu·开源·开源软件