news 2026/9/4 8:01:41

补:Reactor反应堆模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
补:Reactor反应堆模式

四、OneThreadOneLoop 扩展

单 Reactor 虽然简洁,但所有 I/O 和业务处理都在一个线程中完成,无法充分利用多核 CPU。

One Thread One Loop模式通过让每个线程或进程运行一个独立的事件循环来解决这个问题,是从单 Reactor 向高并发架构扩展的自然路径。

4.1 OTOL 核心思想

  • 每个线程/进程维护自己独立的事件循环和 epoll 实例,彼此互不干扰。

  • 每个 Reactor 独立管理自己的 socket 完整生命周期,不涉及 I/O 穿插或乱序问题。

  • 各 Reactor 管理的连接和文件描述符不重复,避免竞争。

  • 只需将单 Reactor 写好,扩展为多进程或多线程的成本很低。

4.2 多进程方案

多进程方案中,Master 进程负责监听 listen socket,通过管道将新连接或就绪事件分发给多个 Slaver 子进程。

方案一采用 Listener 模块方式

Master 只负责监听 listen socket,给 connection 设置的回调函数仅是负载均衡地向管道发送就绪事件;Slaver 把管道读端封装为 Connection 加入自己的 Reactor,回调方法是 Accepter,由 Slaver 自行 accept 新连接。

图3:OTOL 多进程方案一——Master 通过管道发送就绪事件,Slaver 自行 accept

方案二将 accept 动作上移到 Master

Master 监听 listen socket,在回调中一轮获取全部新连接的 sockfd,然后将所有 fd 按负载均衡策略投递到指定管道;Slaver 从管道读取所有 sockfd,直接添加到自己的 Reactor 中进行 I/O 处理。

这种方式更简单,因为多线程可以共享文件描述符,只需修改 connection 的回调方法即可。

图4:OTOL 多进程方案二——Master 获取 fd 并通过管道投递,Slaver 直接添加到 Reactor

从管道读取 sockfd 时,按 4 字节固定长度读取,自动完成序列化和反序列化。

多进程间也可以定义多个管道,用 vector 管理一组 pipefd 结构。

4.3 多线程方案一

管道通信

管道不仅能用于进程间通信,同样适用于线程间。

多线程方案一与多进程方案结构类似,Master 线程通过管道向 Worker 线程传递数据或事件。

下面是一个管道线程间通信的基础示例:

#include <iostream> #include <thread> #include <unistd.h> #include <cstring> int pipefds[2]; void worker_thread() { char buffer[256]; int n; while (true) { n = read(pipefds[0], buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; std::cout << "Worker received: " << buffer << std::endl; } else if (n == 0) { std::cout << "No more data. Exiting." << std::endl; break; } else { std::cerr << "Error reading from pipe." << std::endl; break; } } } int main() { if (pipe(pipefds) == -1) { perror("pipe"); return 1; } std::thread worker(worker_thread); const char* messages[] = {"Hello!", "Another msg.", "Goodbye!"}; for (const char* msg : messages) { sleep(1); if (write(pipefds[1], msg, strlen(msg)) < 0) { perror("write"); break; } std::cout << "Main sent: " << msg << std::endl; } worker.join(); close(pipefds[0]); close(pipefds[1]); return 0; }

4.4 多线程方案二

eventfd 驱动

如果不想使用管道,可以选择 eventfd 作为线程间事件通知机制。

方案二中,Master 线程负责监听 listen socket 并 accept 新连接,将获取到的所有 sockfd 存入一个共享队列,然后通过 eventfd 通知某个 Slaver 线程。

Slaver 线程的 Reactor 监听 eventfd 的读端,回调触发后从共享队列中取出所有新 sockfd,添加到自己的 Reactor 中。

图5:OTOL 多线程方案二——eventfd 通知 + sockfd 共享队列

流程分为五步:Master accept 新连接并将 fd 入队 → Master 通过 eventfd 通知 Slaver → Slaver 的 eventfd 就绪 → Slaver 回调执行,从队列中获取所有 sockfd → 将新 sockfd 添加到自己的 Reactor 中。


五、Eventfd 事件通知机制

5.1基本原理

eventfd 是 Linux 提供的一种轻量级事件通知机制,本质上是一个由内核维护的 64 位计数器,以文件描述符的形式呈现。

它可以与 epoll 等 I/O 多路复用机制结合使用:write 操作增加计数器值,read 操作读取并减少计数器值。

#include <sys/eventfd.h> int eventfd(unsigned int initval, int flags);

5.2 两种工作模式

eventfd 有两种工作模式,区别在于 read 时计数器的减少方式:

模式

read 行为

适用场景

普通模式

一次 read 将计数器值全部读出并清零

仅需事件通知,不关心具体计数

EFD_SEMAPHORE

每次 read 计数器减 1,返回值恒为 1

需要信号量语义,精确控制消费次数

下面的代码分别写入 1 和 2(计数器累计为 3),然后连续读取三次,对比两种模式的输出差异:

eventfd 两种模式对比测试

int main() { // 模式1:普通模式,不设置 EFD_SEMAPHORE int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); // 模式2:信号量模式 // int efd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK | EFD_CLOEXEC); uint64_t value; value = 1; write(efd, &value, sizeof(value)); // 写入1 value = 2; write(efd, &value, sizeof(value)); // 写入2 value = 0; read(efd, &value, sizeof(value)); printf("Read value: %llu\n", (unsigned long long)value); value = 0; read(efd, &value, sizeof(value)); printf("Read value: %llu\n", (unsigned long long)value); value = 0; read(efd, &value, sizeof(value)); printf("Read value: %llu\n", (unsigned long long)value); close(efd); return 0; }

普通模式下,第一次 read 就把累计值 3 全部读出,计数器清零,后续 read 返回 0:

模式1(普通模式)输出

Wrote 1 to eventfd Wrote 2 to eventfd Read value: 3 (counter is now 2) Read value: 0 (counter is now 1) Read value: 0 (counter is now 0)

信号量模式下,每次 read 计数器减 1,返回值恒为 1,三次 read 分别消费一个计数:

模式2(EFD_SEMAPHORE)输出

Wrote 1 to eventfd Wrote 2 to eventfd Read value: 1 (counter is now 2) Read value: 1 (counter is now 1) Read value: 1 (counter is now 0)

在 Reactor 扩展中,eventfd 仅用于事件通知,不传递具体消息内容,因此选择普通模式即可。

5.3 特点与注意事项

主要特点:

  • 低开销:内部是一个 64 位计数器,内核维护成本低。

  • 支持多路复用:可与 epoll、poll、select 结合使用。

  • 原子性:读写操作是原子的,适合高并发场景。

  • 高效性:相比传统管道,避免了多次数据拷贝,内核开销更小。

  • 广播通知:可用于多对多的事件通知,不限于点对点通信。

注意事项:

  • eventfd 仅用于事件通知,不能传递具体的消息内容。

  • 建议设置 EFD_NONBLOCK,避免阻塞操作。

  • 需要信号量语义时设置 EFD_SEMAPHORE,每次读取计数器减 1。

  • 使用完毕后记得关闭文件描述符,防止资源泄漏。

  • 高并发场景下建议结合 epoll 使用,充分发挥事件驱动优势。

六、总结

Reactor 模式通过事件循环 + I/O 多路复用 + 回调分发,在单线程内实现了高并发网络处理。

在处理流程中,ET 模式下读写都需要循环到 EAGAIN;业务处理遵循"提取报文 → 反序列化 → 业务计算 → 序列化 → 封装报文 → 写入发送缓冲区"的六步流水线。

掌握单 Reactor 的完整实现后,向 OTOL 扩展只需关注连接分发和事件通知机制,核心的 Reactor 处理逻辑无需改动,这也正是 Reactor 模式可扩展性的体现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 8:00:43

从面向对象到面向意图:提示词工程与领域建模的融合

从面向对象到面向意图&#xff1a;提示词工程与领域建模的融合 在传统软件工程中&#xff0c;“面向对象编程&#xff08;OOP&#xff09;”与“领域驱动设计&#xff08;DDD&#xff09;”是指导复杂业务系统建模的核心方法论&#xff1a;我们通过类&#xff08;Class&#xf…

作者头像 李华
网站建设 2026/9/4 7:56:34

OpenCV+SVM车牌识别全流程详解:从图像处理到机器学习实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 7:52:02

基于微信小程序的学生课后托管管理系统设计与实现源码+文档

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/4 7:47:41

基于STM32与OpenMV的自动泊车系统:嵌入式视觉与控制实战

简介&#xff1a;本资源是一套面向嵌入式开发学习者与电子设计竞赛参赛者的完整自动泊车系统实现方案&#xff0c;聚焦STM32与OpenMV协同开发&#xff0c;解决智能车辆视觉识别与运动控制融合的关键问题&#xff0c;适用于课程设计、电赛备赛及毕业设计等实践场景。压缩包共110…

作者头像 李华