当前位置: 首页 > news >正文

多线程 Reactor 模式

目录

多线程 Reactor 模式的核心动机

多线程演进方向

多线程 Reactor 模型结构

多线程 EchoServer 实现核心部分

Handler 的多线程化

多线程 Reactor 的三个核心点


本篇文章内容的前置知识为 单线程 Reactor 模式,如果不了解,可点击链接学习
单线程 Reactor 模式-CSDN博客

多线程 Reactor 模式的核心动机

原因:单线程 Reactor 中,Reactor 线程和所有 Handler 处理器都共用同一个线程,一旦某个 Handler 执行时间较长,所有事件的处理都会被阻塞,系统无法横向扩展。
目标:将 Reactor 和 Handler 的职责分离到多个线程中,提高并发能力,适应高连接数和重业务处理场景。

多线程演进方向

(1)升级 Handler:
将负责 IO 读写和业务逻辑的 Handler 放入独立的线程池中异步处理,提高吞吐。
(2)升级 Reactor:
使用多个 Selector,创建多个子反应器(SubReactor),每个负责独立的选择操作。

多线程 Reactor 模型结构

客户端请求
   ↓
Reactor1(主反应器)  →  accept handler(接收连接)
   ↓ 分发给
Reactor2(子反应器)  →  read/decode/compute/encode/write handler

Reactor 1 专职监听新连接事件;
Reactor 2 专职监听 IO 读写事件;
多个 Handler 在不同线程池中处理,互不阻塞。

多线程 EchoServer 实现核心部分

Reactor 主类

Selector[] selectors = new Selector[2];

一个 selector 专管连接接收(OP_ACCEPT);
一个 selector 专管数据读写(OP_READ/WRITE)。

SubReactor subReactor1 = new SubReactor(selectors[0]);
SubReactor subReactor2 = new SubReactor(selectors[1]);

为每个 selector 启动一个线程,轮询事件。

SubReactor 逻辑
子反应器的职责是:
负责轮询 selector 中是否有事件发生;
若有事件触发,就调用 dispatch() 方法,执行 handler。

SelectionKey sk = it.next();
Runnable handler = (Runnable) sk.attachment();
handler.run();

从 selectionKey 中拿出 attach 的 Handler;
直接执行 handler 的 run() 方法。

AcceptorHandler(连接处理器)

SocketChannel channel = serverSocket.accept();
new MultiThreadEchoHandler(selectors[1], channel);

接收连接后创建 IO Handler;
并将其注册到数据读写 selector(selectors[1])中。

Handler 的多线程化

目标:将读写处理逻辑从 IO 事件轮询线程中分离,提交到线程池异步处理。

核心实现:

static ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> asyncRun());

static ExecutorService pool = Executors.newFixedThreadPool(4);
创建一个包含 4 个线程的固定大小线程池,专门用于执行具体的业务逻辑(如数据处理、计算等)。线程池会复用这 4 个线程,避免频繁创建销毁线程的开销。

pool.submit(() -> asyncRun());
将实际的业务处理逻辑(asyncRun() 方法,比如读取数据、回显数据的逻辑)提交到线程池执行。这样做的核心目的是 让 Reactor 线程(负责监听和分发事件)不用等待业务处理完成,可以立即回去处理其他 IO 事件,避免被耗时操作阻塞。

asyncRun() 方法 的代码思路和单线程 Reactor 模式的核心 run() 方法 代码思路类似

单线程 Reactor 模式因 单线程瓶颈,仅适用于简单场景;
Connection Per Thread 因 线程爆炸 问题,已被淘汰;
多线程 Reactor 模式通过 事件分离 + 线程池 解决了前两者的缺陷

多线程 Reactor 的三个核心点

模块处理职责所在线程
Main Reactor监听连接(OP_ACCEPT)主线程
Sub Reactor监听读写事件(OP_READ / OP_WRITE)线程1、线程2(子线程)
Handler(多线程)实际执行 IO + 业务逻辑提交给线程池异步执行

优势对比

模型是否并发是否线程隔离是否支持线程池
单线程 Reactor
多线程 Reactor

Reactor 模式的优缺点

一、Reactor 模式与其他模式对比
(1)Reactor 模式 vs 生产者-消费者模式
相似之处:
二者都具有事件驱动特性。
在生产者-消费者模型中:
生产者产生任务(事件)并放入一个队列。
消费者从队列中拉取(pull)任务并处理。

Reactor 模式下:
客户端连接、读写事件相当于生产者。
Selector 检查事件是否就绪,相当于从“系统缓冲队列”中拉取事件。
Handler 负责处理事件,相当于消费者。

不同之处:
生产者-消费者模型是基于「消息队列」的显式交互;
Reactor 模式是基于 IO 多路复用的「查询式机制」(如 select、epoll)
Reactor 没有明确的队列,而是通过系统底层缓存区查询到事件,再立即由 Dispatcher 分发给对应 Handler 处理。

(2)Reactor 模式 vs 观察者模式
相似之处:
两者都体现了事件监听 + 响应处理的思想。
Reactor 中,Selector 检测到事件 → Dispatcher 分发给对应 Handler。
观察者模式中,主题 Topic 发生变化 → 通知所有订阅该主题的观察者 Observer。
都涉及“注册监听 + 回调响应”的机制。

不同之处:
观察者模式允许一个事件被多个观察者处理(一对多)
而 Reactor 模式中,一个 IO 事件只能交给一个 Handler 实例处理(一对一)
即:事件和处理器的绑定是唯一的,Handler 和 SelectionKey 是一一对应的。

二、Reactor 模式的优点
高响应性:
单线程模式:通过 非阻塞 IO + 事件驱动,避免了传统阻塞 IO 中 一个连接阻塞导致全系统卡住 的问题(仅当 Handler 无阻塞操作时有效)
多线程模式:进一步通过 Reactor 线程与业务线程分离,即使某个业务逻辑阻塞,也不会影响 Reactor 线程处理其他连接,响应性更优。

编程简单:
单线程模式:天然无多线程共享数据问题,不需要锁和同步,逻辑最简洁。
多线程模式:通过 职责分离(Reactor 线程管 IO,业务线程管逻辑),减少了共享数据的场景,相比 一连接一线程 模式,锁和同步的复杂度更低。

良好的可扩展性:
多线程模式:可通过增加 Reactor 线程数(利用多核 CPU)、扩大业务线程池(处理更多并发业务)显著提升吞吐量,扩展性更强。

三、Reactor 模式的缺点
模型实现较复杂:
相较于简单的阻塞 IO,Reactor 涉及 Selector 注册、事件派发、状态管理,门槛较高;
不易调试,错误不易定位。

强依赖系统支持:
依赖于操作系统提供的高效 IO 多路复用机制(如 Linux 的 epoll);
若底层不支持 IO 复用,性能优势无法体现,甚至可能适得其反。

Handler 内部阻塞会影响整体反应能力:
如果某个 Handler 内部业务逻辑阻塞太久(比如读取大文件、等待数据库响应),会导致整个 Selector 线程卡住,从而影响其他 IO 事件的派发,形成伪非阻塞。

模式特点是否规避伪非阻塞?
多 Reactor + 同步 Handler多线程负责 Selector,Handler 仍由 Selector 线程执行不能规避
多 Reactor + 异步 Handler(线程池)Selector 线程只分发事件,Handler 放入线程池处理能完全规避

同步异步handler主要是看它的run方法里面有没有包含耗时/阻塞操作,比如read() / write()

伪非阻塞 指的是表面上使用了非阻塞 IO(如 NIO 的 SocketChannel 配置为非阻塞模式)和 Reactor 事件驱动模型,但由于某个环节(通常是 Handler 处理逻辑)出现阻塞,导致整个 Reactor 线程被卡住,最终失去了非阻塞模式应有的并发处理能力。

http://www.lryc.cn/news/598807.html

相关文章:

  • hcip思维导图(1)
  • GaussDB 数据库架构师(八) 等待事件概述-1
  • 阿里云ECS坑之dnf-makecache系统软件更新检测服务
  • 解决postgresql连接数不足
  • 五分钟了解Java 中的锁
  • SQL基础⑪ | 约束
  • JavaScript 中的 structuredClone() 如何彻底改变你的对象复制方式
  • Android LiveData 全面解析:原理、使用与最佳实践
  • Windows 10 远程桌面(RDP)防暴力破解脚本
  • Android 与 Windows 文件路径的设计差异
  • Android Camera createCaptureSession
  • 教程:如何通过代理服务在国内高效使用 Claude API 并集成到 VSCode
  • DGMR压缩技术:让大规模视觉Transformer模型体积减半而性能不减
  • FastAPI中间件
  • iview 部分用法
  • 锁定锁存器 | 原理 / 应用 / 时序
  • 哈希表模拟实现
  • JVM 垃圾收集器CMS和G1
  • HTTP性能优化实战:从协议到工具的全面加速指南
  • 服务端对接 HTTP 接口传输图片 采用base64还是 multipart/form-data
  • 排序初识(上)-- 讲解超详细
  • Android Studio历史版本快速下载(二次修改记录)
  • rna_seq_pipeline.py-python002
  • CloudComPy使用PyInstaller打包后报错解决方案
  • 如何使用 pdfMake 中文字体
  • 【Oracle APEX 】示例应用库无法访问
  • 对称密码算法详解:从DES到AES的加密演进
  • Lua协同程序(coroutine)
  • C11补充
  • 力扣20:有效的括号