Linux 高级IO(七)多进程、多线程的Reactor反应堆模式扩展、OTOL

目录

[一、Reactor 反应堆模式](#一、Reactor 反应堆模式)

[什么是 Reactor 反应堆模式?](#什么是 Reactor 反应堆模式?)

[单 Reactor 单线程模型](#单 Reactor 单线程模型)

[二、扩展多进程/多线程 Reactor 模型](#二、扩展多进程/多线程 Reactor 模型)

什么是OTOL?

[扩展为多进程 Reactor 模型](#扩展为多进程 Reactor 模型)

[扩展为多线程 Reactor 模型](#扩展为多线程 Reactor 模型)

其他

三、总结


本篇文章紧接这一篇文章将进行叙述,总结 Reactor 反应堆模式以及进行多进程与多线程化扩展。

一、Reactor 反应堆模式

什么是 Reactor 反应堆模式?

Reactor 反应堆模式是一套基于 IO 多路复用的事件驱动并发编程模型,核心依靠 EventLoop 事件循环统一监听所有文件描述符的 IO 就绪事件,当内核通知某个 fd 产生可读、可写或者异常事件时,事件循环会将事件分发至对应连接的业务处理函数执行读写逻辑,整套架构不需要为每一条客户端 TCP 连接单独创建线程或者进程,所有连接的 IO 操作都由同一个事件循环线程串行调度,极大降低多线程上下文切换、线程资源抢占带来的性能开销,能够高效支撑上万并发连接,除网络 IO 事件之外,该模型也能够拓展支持定时器、信号等其他类型事件,是 Linux 后端高性能网络服务最主流的设计方案。

关于 Reactor "反应堆" 这个名称的由来,可以类比核反应堆的工作机制,我们可以用通俗的逻辑对应起来理解,核反应堆内部持续存在大量核反应粒子,反应堆的控制中枢持续监控粒子反应状态,一旦出现可控的反应信号就启动对应调节装置处理,而 Reactor 模型里,epoll 实例就相当于反应堆的监控核心,所有注册的套接字 fd 等同于待监控的反应粒子,操作系统内核持续监测每一个 fd 的缓冲区状态,一旦 fd 出现 IO 就绪事件,就如同反应堆产生反应信号,EventLoop 捕获信号后立刻分发事件、调用读写函数完成数据处理,整个模型被动等待事件产生、事件抵达后再响应处理的运行逻辑,和反应堆被动响应内部粒子反应的工作模式高度相似,因此设计者借用反应堆这个名词命名该事件驱动模型,只是软件层面的逻辑类比,仅仅是形象化的命名方式。

单 Reactor 单线程模型

再结合我们上一篇的计算器网络服务代码来看,这个代码属于标准的单 Reactor 单线程模型,也就是单 Reactor 架构。我们的代码全局只创建唯一的 Reactor 实例,这个 Reactor 内部持有唯一的 Epoller 对象,底层仅生成一个 epoll 内核实例,整个程序仅有主线程运行 Reactor 的 Loop 事件循环,所有类型的连接,包括 Listener 监听套接字、每一个客户端 IOManager 套接字,全部注册进这同一个 epoll 实例,所有的读事件、写事件、异常事件都由这一条主线程内的 epoll_wait 统一捕获,事件捕获完成之后,主线程内串行执行事件分发逻辑,依次调用 Listener、IOManager 重写的 Recver、Sender、Excepter 函数完成网络读写,再同步执行 Protocol 协议解析、Calculator 数学运算等全部业务逻辑,全程没有开启任何额外工作线程分担 IO 或计算任务,不存在多 Reactor 实例、多事件循环线程的设计,完全贴合单 Reactor 架构的核心特征。 同时我们可以简单补充单 Reactor 架构的优势与局限来完善总结,单 Reactor 架构代码实现简洁、没有多线程同步互斥的复杂逻辑,开发与调试难度低,非常适合我们当前计算器这种业务计算耗时极短的轻量服务;但如果后续业务存在耗时很长的计算逻辑,长时间占用主线程事件循环,就会阻塞 epoll_wait 事件捕获,造成新的网络 IO 事件无法及时响应,这种场景就需要升级为多 Reactor 多线程模型,将耗时业务剥离至工作线程池,以此保障网络事件调度不会被阻塞。

二、扩展多进程/多线程 Reactor 模型

什么是OTOL?

One Thread One Loop 简称 OTOL,就是一个线程绑定独立一套事件循环,这是事件驱动 IO 框架底层通用的基础执行规范,它的核心约束是任意一条事件循环 EventLoop 仅能由固定的单一线程运行,一个线程内也只会维护唯一的 epoll 实例与事件循环,线程和事件循环一一绑定,不会出现跨线程操作同一个 EventLoop、多个线程争抢同一套 IO 事件调度的情况,依靠这个规范可以彻底规避多线程操作文件描述符、缓冲区时产生的竞态、乱序 IO 等同步问题,框架不需要大量互斥锁来保护连接与事件资源,大幅简化并发代码的开发与调试成本。

OTOL 和 Reactor 的关系?

OTOL 属于底层线程调度规范,而 Reactor 是基于 IO 多路复用的事件驱动架构模型,二者是基础规则与上层架构的从属关系,所有标准 Reactor 实现都会遵循 OTOL 规则,我们此前完成的单 Reactor 代码就是典型的单线程单 Loop,完全贴合 OTOL 思想,后续要扩展的多进程 Reactor 方案,本质是把 OTOL 的思想从单进程内的多线程,延伸到多进程维度,每一个子进程内部都维持独立的线程与独立 Reactor 事件循环,进程之间的 Loop 完全隔离。

扩展为多进程 Reactor 模型

如上图所示,是一个 Master 主进程、多个 Slave 子进程的架构,我们基于现有的单 Reactor 代码完成多进程扩展,整体分为架构划分、资源预创建、进程派生、连接分发、子进程接管 IO 五大步骤。

第一步做进程职责拆分,设置唯一 Master 主进程和若干 Slave 工作子进程,Master 进程只持有监听套接字 listenfd 与自身的 Reactor,它的 Reactor 仅用来监控 listenfd 的读就绪事件,完全不处理任何客户端数据读写业务;每个 Slave 子进程内部都拥有一套完整独立的 Reactor、专属 epoll 实例,遵循 OTOL 规则,专门负责接管客户端连接后的双向数据收发、协议解析与业务运算,各个子进程的 fd、连接、事件循环彼此完全隔离,不存在共享资源。

第二步预先创建进程间通信管道,Master 和每一个待创建的 Slave 子进程提前生成一组 pipe 管道,管道的写端留在 Master 进程,读端会随着 fork 系统调用继承到对应子进程中,子进程会把管道读端封装成我们项目里的 Connection 对象,注册进自身 Reactor,绑定专门的 Accept 回调,用来接收 Master 分发的新连接通知。

第三步执行 fork 派生子进程,fork 调用时所有子进程都会继承父进程的 listenfd 监听套接字,同时继承对应管道的读端文件描述符,子进程完成资源继承后不会再新建其他进程,全程运行自身独立的 Reactor 事件循环,Master 进程则保留所有管道写端,负责后续的连接负载均衡分发。

第四步 Master 进程完成新连接的负载均衡分发,当 listenfd 触发读就绪事件,代表有新客户端发起 TCP 握手,Master 不会调用 accept 获取新连接,而是根据预设的负载均衡策略,选择当前连接数量最少的 Slave 子进程,通过二者绑定的管道向子进程写入一段通知数据,唤醒该子进程内部管道读端对应的 Accept 回调。

第五步 Slave 子进程接管新连接并自主处理全生命周期 IO,子进程收到管道传来的通知信号后,调用 accept 获取客户端新连接 fd,把这个 fd 封装为 IOManager 对象注册进自身的 Reactor,自此这条客户端连接的所有读、写、异常事件都由当前子进程的 Reactor 独立调度处理,从数据 recv/send、协议封包解包到 Calculator 业务运算全部在本进程内闭环完成,不会和其他子进程产生 IO 穿插,天然规避多进程资源争抢问题,整套扩展方案依托原有单 Reactor 的完整读写、协议、业务逻辑,仅新增 Master 分发、管道进程通信、进程 fork 资源继承三层上层逻辑,复用绝大多数已有代码。

扩展为多线程 Reactor 模型

这套多线程方案的核心思路和多进程架构是同源的,同样遵循 OTOL "一个线程一个独立事件循环" 的规则,只是把 Master-Slave 的进程模型改成了线程模型,主线程作为 Master,工作线程作为 Slave,线程之间通过管道传递新连接的文件描述符,实现连接的负载均衡分发,每个线程都拥有独立的 Reactor 事件循环,彼此不共享任何 IO 资源,避免多线程同步问题。

首先,架构上依然是主线程负责监听,工作线程负责业务 IO。主线程持有监听套接字 listenfd 和自身的 Reactor,它的 Reactor 只监听 listenfd 的读就绪事件,不处理任何客户端数据读写,当有新连接到来时,主线程调用 accept 获取新的客户端 fd,然后根据预设的负载均衡策略,把这个 fd 通过管道发送给指定的工作线程。每个工作线程内部都创建一套完整独立的 Reactor 和 epoll 实例,运行独立的事件循环,遵循 OTOL 规则,专门处理分配给自己的客户端连接的读写、协议解析和业务运算,线程之间的连接、事件循环完全隔离,没有共享资源,也就不存在多线程竞态的问题。

和多进程方案不同的是,多线程方案里,所有线程都属于同一个进程空间,因此不需要 fork 继承 listenfd,listenfd 只在主线程中创建即可,同时线程间的通信同样可以使用管道,主线程把管道的写端留在自身,每个工作线程保留管道的读端,工作线程会把管道读端封装成 Connection 对象,注册进自身的 Reactor,绑定读取回调,用来接收主线程发来的新连接 fd。当主线程通过管道写入客户端 fd 后,工作线程的管道读端会触发读事件,回调函数读取 fd 并封装为 IOManager 对象,注册进自身的 Reactor,后续这条连接的所有 IO 事件都由当前工作线程独立处理,从 recv/send 到业务运算全部在本线程内闭环完成,不会和其他线程产生 IO 穿插。

整体来看,多线程方案和多进程方案的核心逻辑高度一致,都是基于 OTOL 规则的 Master-Slave 分发模型,只是把进程换成了线程,复用了管道传递 fd 的通信方式,保留了单 Reactor 的业务处理逻辑,只增加了主线程分发和线程间通信的上层逻辑,实现了单进程内的多事件循环并发,既提升了并发处理能力,又避免了跨进程调度的开销。

其他

除了刚刚讲的 Master 分发连接的多进程 / 多线程 OTOL 方案,业界还有另外几种主流 Reactor 扩展方案,我分两类简单连贯说明。

第一种是主从 Reactor 多线程模型,它和上面管道分发 fd 的方案有区别,整体还是单进程,分出一条主线程 Main Reactor 只负责监听 listenfd、accept 拿到新连接,拿到连接之后,通过负载均衡把连接 fd 投递到某个子 Reactor 工作线程的任务队列;子 Reactor 线程各自拥有独立 epoll 与事件循环,只负责处理分到自己手里的客户端读写、业务逻辑。和管道方案的差异在于,它依靠任务队列做线程通信,而非管道传递 fd,适合业务计算轻微、追求低进程切换开销的场景,是 muduo 等网络库的默认方案。

第二种是多 Reactor 多进程池方案,完全放弃统一 Master 监听,每个进程内部自带完整 Reactor,全部进程都绑定同一个端口 listenfd,依靠操作系统内核的 accept 惊群处理、加上互斥锁竞争 accept,哪个子进程抢到锁就获取新连接,连接直接交给本进程 Reactor 处理。这套方案不需要主进程分发连接,架构更简单,但是并发连接暴涨时会出现大量进程锁竞争,惊群也会带来轻微性能损耗,适合业务简单、对并发峰值要求不高的服务。

第三种是单 Reactor 配合线程池的混合方案,底层只有一套 epoll 事件循环运行在主线程,所有套接字的读写事件全部由主线程的 Reactor 捕获,一旦遇到耗时的业务计算、文件 IO 等操作,不阻塞主线程,而是把业务任务抛给全局线程池异步执行,计算完成后再把应答数据发回主线程,由主线程调用 send 发送。这种方案不用多套 Reactor,改造原有单 Reactor 代码成本最低,但网络 IO 调度始终只有一条线程,海量连接并发读写时会出现主线程瓶颈,只适合大部分业务逻辑快速完成、少量耗时操作的场景。

第四种是 IO 多路复用结合协程的方案,不基于多 Reactor 线程 / 进程,在单线程单个 epoll 的基础上使用协程封装同步风格读写接口,每个客户端连接对应一条协程,epoll 事件就绪时调度对应协程执行收发逻辑。它规避了多线程同步开销,代码写法更直观,不过底层依旧依托单 Reactor,重度计算任务依然会阻塞整个事件循环,需要搭配线程池卸载计算任务。

三、总结

本文系统讲解了Reactor反应堆模式及其扩展方案。首先阐述了Reactor模式的核心原理:基于IO多路复用的事件驱动模型,通过单线程事件循环统一调度所有连接IO,类比核反应堆机制进行形象化命名。接着分析单Reactor单线程模型的实现特点及适用场景。然后重点介绍多进程/多线程扩展方案:遵循"单线程单事件循环"(OTOL)原则,通过Master-Slave架构实现连接分发,详细说明了多进程方案的五个关键步骤和多线程方案的对应实现。最后对比了主从Reactor多线程、多Reactor多进程池、单Reactor+线程池、IO多路复用+协程等四种扩展方案的优缺点及适用场景。全文从基础原理到扩展实践,构建了完整的Reactor模式知识体系。

谢谢大家的观看!

相关推荐
实心儿儿1 小时前
Linux —— 线程互斥和同步
linux
梦想的颜色1 小时前
Docker 入门指南:从零开始掌握容器化技术
运维·服务器·vscode·python·算法·docker·云原生
晚风吹红霞1 小时前
C++ list 容器完全指南:从入门到手撕双向链表
c++·链表·list
handler011 小时前
【Linux 网络】:poll/epoll 底层机制与 Reactor 并发模型
linux·运维·服务器·网络·c++·多路转接·多路复用
kebidaixu1 小时前
深入解析 Linux GPIO 采集与控制程序(DI/DO 篇)
linux
jiayong231 小时前
CI/CD与DevOps、Jenkins、K8s关系深度解析
运维·git·ci/cd
cpp_25011 小时前
P10109 [GESP202312 六级] 工作沟通
数据结构·c++·算法·题解·洛谷·gesp六级
霸道流氓气质1 小时前
导入历史跟踪机制实战指南
java·linux·服务器
Xeon_CC1 小时前
vs2026远程开发debian12容器的C++程序笔记
开发语言·c++·笔记