Linux高性能网络编程——C++11实现22种高并发模型
- 虽然有很多冗余而且不常用,但其实是一个循序渐进的脉络,是一个体系
github代码地址:https://github.com/linkxzhou/mylib/tree/master/c%2B%2B/concurrency_server
1. SingleProcess (单进程模型)
一个进程死命干,串行处理,一次只能处理一个请求
2. MultiProcess (多进程模型)
用fork创建多个子进程一起干
注意最后要通过signal处理或waitpid()回收僵尸进程。
3. MultiThread (多线程模型)
把进程换成线程呗用pthread_create()
注意最后为了避免线程泄漏,通常采用detached线程或在主线程中join回收。
4. ProcessPool1 (进程池模型1)
预先创建固定数量的工作进程,共享监听socket
5. ProcessPool2 (进程池模型2 - SO_REUSEPORT)
使用SO_REUSEPORT选项,每个进程独立监听同一端口
优点: 内核负载均衡,避免惊群效应
缺点: 依赖操作系统特性,可移植性有限
6. ThreadPool (线程池模型)
预先创建固定数量的工作线程
优点: 避免频繁创建线程,资源利用率高
缺点: 线程数固定,可能无法适应负载变化
7. LeaderAndFollower (领导者/跟随者模型)
特点: 线程池变种,一个线程作为leader监听连接
- 实现架构:
架构基于线程池和角色管理机制构建。
系统维护一个线程池,其中一个线程担任领导者,其余线程为跟随者。
领导者线程负责在事件多路复用器(如epoll)上等待I/O事件。
当事件到达时,领导者首先从跟随者中选择一个线程提升为新领导者,然后自己降级为工作线程处理该事件,角色切换通过条件变量和互斥锁实现同步。
为了避免惊群效应,只有领导者线程在事件多路复用器上等待。
处理完事件的线程会重新加入跟随者队列等待下次被选为领导者。
8. Select (Select I/O多路复用)
略
9. Poll (Poll I/O多路复用)
略
10. Epoll (Epoll I/O多路复用)
略
11. Kqueue (Kqueue I/O多路复用)
BSD/macOS特有的高效I/O多路复用机制
Kqueue是FreeBSD和macOS系统提供的高性能事件通知机制,类似于Linux的epoll但功能更强大。
Kqueue不仅支持网络I/O事件,还支持文件系统变化、信号、定时器等多种事件类型。通过kevent()系统调用统一管理所有事件,提供了一致的编程接口。
Kqueue使用内核事件队列,只通知发生变化的事件,避免了轮询开销。
其设计哲学是提供统一的事件处理框架,让应用程序能够高效地响应各种系统事件。
优点: 性能优秀,功能丰富
缺点: 仅限BSD/macOS系统
12. Reactor (Reactor模式)
基本流程
主线程(或少量固定线程)使用epoll(或select、kqueue等)监听所有I/O事件。
当有I/O事件(如可读、可写)发生时,主线程直接在事件回调里同步处理所有业务逻辑(如读取数据、解析、响应等)。
处理完后,主线程继续监听下一个事件。
特点
所有操作都在主线程(或少量线程)中完成,包括I/O和业务处理。
优点:实现简单,避免多线程同步问题。
缺点:如果业务处理耗时,主线程会被阻塞,影响I/O响应能力,不适合业务处理复杂或CPU密集型场景。
13. Coroutine (协程模式)
详细介绍: 协程模式通过状态机模拟协程行为,在C++11环境下实现异步编程。与传统的回调方式不同,协程允许函数在执行过程中暂停并在稍后恢复,使异步代码看起来像同步代码。
本实现使用状态机来跟踪每个连接的处理状态,当遇到会阻塞的I/O操作时,协程会yield让出控制权,等待I/O就绪后再resume继续执行。
这种模型特别适合处理大量并发连接,因为协程的内存开销远小于线程,可以创建成千上万个协程而不会耗尽系统资源。
- 实现架构:
架构基于状态机和事件循环构建。
每个客户端连接对应一个协程对象,包含当前状态、上下文数据和状态转换逻辑。协程调度器维护所有活跃协程的列表,在事件循环中轮询I/O事件。
当socket就绪时,调度器恢复对应协程的执行。
协程内部使用状态机实现:INIT状态初始化连接,READING状态处理读取,PROCESSING状态处理业务逻辑,WRITING状态发送响应,DONE状态清理资源。
每个状态都可能因为I/O阻塞而yield,调度器会在下次循环中检查并恢复。
14. Actor模型
每个Actor是独立的计算单元,通过消息传递通信
适用场景: 分布式系统、高并发消息处理
优点: 无共享状态,天然避免竞态条件
缺点: 实现复杂,消息传递开销
实现: 可以基于线程池 + 消息队列实现
- 详细介绍:
Actor模型是一种基于消息传递的并发计算模型,每个Actor都是独立的计算单元,拥有自己的状态和行为。
Actor之间不共享内存,只能通过异步消息进行通信。
当Actor接收到消息时,可以执行三种操作:处理消息并更新内部状态、向其他Actor发送消息、创建新的Actor。
这种模型天然避免了传统并发编程中的锁和竞态条件问题,提供了更安全的并发处理方式。
Actor模型特别适合构建分布式系统,因为Actor可以分布在不同的机器上,通过网络进行消息传递。
- 实现架构:
架构围绕Actor、消息队列和调度器构建。
每个Actor包含邮箱(消息队列)、状态数据和消息处理逻辑。
系统启动时创建多个Worker Actor处理客户端请求,一个Acceptor Actor负责接受新连接。
当有新连接时,Acceptor发送消息给负载最轻的Worker。
Worker Actor接收到连接消息后,负责该连接的整个生命周期。
Actor调度器负责从各个Actor的邮箱中取出消息并执行相应的处理函数。
消息传递通过线程安全的队列实现,支持本地和远程消息。
15. 事件循环模型 (Event Loop)
- 事件循环的核心流程
初始化:程序启动,设置好要监听的事件(比如网络端口、定时器等)。
进入循环:进入一个无限循环(while (true))。
等待事件:使用高效的I/O多路复用机制(如epoll、kqueue),等待事件发生。
事件就绪:某个事件发生了(比如有客户端发来数据)。
执行回调:把对应的回调函数加入事件队列,然后依次执行这些回调函数。
继续循环:处理完所有就绪事件后,继续等待下一个事件。
- 举个生活中的例子
想象你是一个服务员(主线程),你要负责整个餐厅的点单、上菜、结账等所有工作(事件)。
你不会一个桌子一个服务员(多线程),而是一个人负责所有桌子。
有顾客举手(事件发生),你就去处理(执行回调)。
如果顾客点菜后要等厨房做菜(I/O),你不会一直站在那等(阻塞),而是记下等会再来(注册回调),先去服务其他桌子。
菜做好了(事件就绪),厨房通知你,你再把菜端过去(执行回调)。
你一直在大厅里转(事件循环),只要有事就去做,没事就等着。
- 为什么要用定时器?
在事件循环的服务器或程序中,定时器的作用是:
在未来的某个时刻自动执行某个任务,比如:
连接超时检测(比如客户端10秒没发消息就断开)
定时发送心跳包
定时清理缓存
定时执行某些业务逻辑(比如每隔1分钟统计一次数据)
举例:
你写了一个聊天服务器,要求“如果用户5分钟没发消息就自动下线”。这就需要一个定时器,5分钟后检查用户是否活跃。
- 为什么用最小堆来管理定时器?
假设你有很多定时任务(比如成千上万个连接,每个都有自己的超时时间),你需要高效地找到下一个要触发的定时器。
传统做法
用一个列表存所有定时器,每次都遍历一遍,找出最近要触发的那个,效率低(O(n))。
最小堆做法
最小堆是一种特殊的二叉树结构,能保证堆顶元素是所有元素中最小的。
把所有定时器按“到期时间”放进最小堆,堆顶就是最近要触发的定时器。
每次只需要看堆顶就知道下一个要处理的定时器是谁,效率高(O(log n)插入/删除,O(1)取最小值)。
举例:
你有5个定时器,分别在2秒、5秒、1秒、10秒、3秒后触发。
用最小堆管理后,1秒的定时器会在堆顶,最先被处理。
新增、删除定时器也很快,不用每次都遍历全部。
16. 纤程/用户态线程 (Fiber/Green Thread)
特点: 用户态调度的轻量级线程
适用场景: 需要大量并发连接的场景
优点: 创建成本极低,可创建数万个
缺点: 需要实现复杂的调度器和栈管理
实现: 需要实现用户态调度器和栈切换
详细介绍: 纤程(Fiber)是一种用户态的轻量级线程,也称为绿色线程或协作式线程。
与操作系统线程不同,纤程的创建、销毁和调度都在用户空间完成,不需要内核参与。
纤程之间采用协作式调度,只有当纤程主动让出控制权时才会发生切换,这避免了抢占式调度的开销和复杂性。
每个纤程只需要很少的内存(通常几KB的栈空间),因此可以创建数十万个纤程而不会耗尽系统资源。
纤程特别适合I/O密集型应用,当遇到阻塞操作时可以快速切换到其他纤程继续执行。
- 实现架构:
架构基于用户态调度器和上下文切换机制构建。
每个纤程包含独立的栈空间、寄存器状态和执行上下文。
纤程调度器维护就绪队列和阻塞队列,负责纤程的创建、调度和销毁。
当纤程遇到I/O操作时,会将自己加入阻塞队列并yield给调度器,调度器选择下一个就绪的纤程继续执行。
I/O完成后,相应的纤程被移回就绪队列等待调度。
上下文切换通过汇编代码实现,保存和恢复CPU寄存器状态。
为了支持异步I/O,通常结合epoll等机制,在I/O就绪时唤醒对应的纤程。
17. 工作窃取模型 (Work Stealing)
这个挺好玩的,文件在: work_stealing_server.h
特点: 每个线程有自己的任务队列,空闲时从其他线程窃取任务
适用场景: CPU密集型任务的负载均衡
优点: 自动负载均衡,减少线程空闲
缺点: 实现复杂,可能存在缓存一致性问题
- 实现:
基于无锁队列和线程池
- 详细介绍:
工作窃取模型是一种动态负载均衡的并行计算模式,每个工作线程维护自己的任务队列,当线程完成自己队列中的任务后,会尝试从其他线程的队列中"窃取"任务来执行。
这种模型能够自动适应任务执行时间的不均匀性,避免某些线程空闲而其他线程过载的情况。
工作窃取算法最初由Cilk项目提出,后来被广泛应用于Java的ForkJoinPool、Intel TBB等并行计算框架中。
该模型特别适合处理递归分治算法和任务执行时间差异较大的场景。
- 实现架构:
架构基于多个工作线程和双端队列(deque)构建。
每个工作线程拥有一个双端队列,新任务从队列头部添加,线程从头部取出任务执行(LIFO顺序,利用缓存局部性)。
当线程的队列为空时,会随机选择其他线程的队列,从尾部窃取任务(FIFO顺序,减少冲突)。
为了减少锁竞争,通常使用无锁的双端队列实现。
任务可以在执行过程中产生新的子任务,这些子任务会被添加到当前线程的队列中。
系统还包含一个全局任务队列,用于接收外部提交的任务。
18. 生产者-消费者模型
信号量+条件变量
19. 半同步/半异步模型 (Half-Sync/Half-Async)
- 详细介绍:
半同步/半异步模型是一种混合架构模式,将系统分为同步处理层和异步处理层,结合两种模式的优势。
异步层负责高效的I/O处理,使用事件驱动的方式处理网络事件;同步层负责业务逻辑处理,使用传统的同步编程模型。
两层之间通过队列进行通信,异步层将接收到的请求放入队列,同步层的工作线程从队列中取出请求进行处理。
这种模型既保证了I/O处理的高效性,又保持了业务逻辑的简洁性,是实际项目中常用的架构模式。
- 实现架构:
架构分为三个主要组件:异步I/O层、同步处理层和队列层。
异步I/O层使用单线程事件循环,基于epoll/kqueue等机制处理所有的网络I/O事件,包括接受连接、读取数据、发送响应。
当完整的请求接收完成后,将请求数据封装成任务对象放入队列。
同步处理层包含多个工作线程,从队列中取出任务,使用传统的同步方式处理业务逻辑,如数据库访问、文件操作等。
处理完成后,将响应数据通过队列传回异步层进行发送。
队列层负责两层之间的通信,通常使用线程安全的队列实现。
20. Proactor模式
Proactor模式的本质
I/O数据的“搬运”(比如从网卡/磁盘搬到内存,或从内存搬到网卡/磁盘)这部分完全交给内核异步完成。
应用程序只需要发起请求,然后等内核通知“搬运好了”,再去处理数据。
这样,应用线程不用自己等、也不用自己搬数据,可以省下等待和搬运的时间,专注于业务逻辑处理。
故:
应用层只处理I/O完成后的业务逻辑。
Proactor模式就是把I/O数据搬运的活交给内核,应用只管处理结果,提升了效率和并发能力。
21. 管道模型 (Pipeline)
- 特点:
请求处理分为多个阶段,每个阶段由不同线程处理
适用场景: 复杂的请求处理流程
优点: 流水线处理,提高吞吐量
缺点: 阶段间同步复杂,可能存在瓶颈阶段
实现: 多个线程池,每个处理一个阶段
- 详细介绍:
管道模型将请求处理过程分解为多个连续的阶段,每个阶段由专门的线程或线程池负责,形成流水线式的处理架构。
请求按顺序通过各个阶段,每个阶段专注于特定的处理任务,如解析、验证、业务逻辑、响应生成等。 这种模型类似于工厂的流水线生产,能够显著提高系统的吞吐量,因为多个请求可以同时在不同阶段并行处理。
管道模型特别适合处理步骤固定、可以分解的复杂业务流程,在数据处理、图像处理、编译器等领域应用广泛。
- 实现架构:
架构由多个处理阶段和阶段间的缓冲队列组成,每个阶段包含一个或多个工作线程,专门负责特定的处理任务。
阶段之间通过线程安全的队列连接,前一阶段的输出作为后一阶段的输入。
请求从第一个阶段开始,依次通过所有阶段,最终产生响应。
每个阶段可以独立调优,包括线程数量、队列大小等参数。
为了避免某个阶段成为瓶颈,需要根据各阶段的处理能力合理配置资源。
可以实现阶段的动态扩缩容,根据负载情况调整线程数量。
22. 混合模型
- Reactor + 线程池模型(epoll + 线程池)
- 基本流程
主线程用epoll监听I/O事件。
当有I/O事件发生时,主线程只做最小化的I/O操作(如读取请求数据),然后把后续的业务处理任务丢给线程池。
线程池中的工作线程异步处理业务逻辑(如解析、数据库访问、生成响应等)。
主线程不会被业务处理阻塞,可以持续高效地响应新的I/O事件。
- 特点
I/O事件分发和业务处理解耦,主线程专注I/O,线程池专注业务。
优点:即使业务处理很耗时,也不会影响主线程的I/O响应能力,适合高并发、业务复杂的场景。
缺点:实现更复杂,需要处理多线程同步、任务队列等问题。
性能对比

更多推荐



所有评论(0)