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

NebulaChat 框架学习笔记:深入理解 Reactor 与多线程同步机制

今天主要整理了 Reactor 框架中几个核心机制,包括 epolleventfdatomicvector.data() 的使用,还有多线程同步中 cv.wait() 的底层逻辑。
这些知识看似细节,实则是写高性能 C++ 网络程序的地基。

一、Reactor::loop() 的事件派发流程

loop() 是 Reactor 的“心脏”,它通过 epoll_wait() 等待内核事件,并把每个事件分发给上层 ServerConnection 对象。

void Reactor::loop() {if (!dispatcher_) return;running_.store(true, std::memory_order_release);while (running_.load(std::memory_order_acquire)) {int n = epoll_wait(epfd_, evlist_.data(), evlist_.size(), -1);for (int i = 0; i < n; ++i) {int fd = evlist_[i].data.fd;uint32_t ev = evlist_[i].events;if (fd == evfd_) { DrainEventfd(evfd_); continue; }void* user = nullptr;{std::lock_guard<std::mutex> lk(users_mtx_);auto it = users_.find(fd);if (it != users_.end()) user = it->second;}dispatcher_(fd, ev, user);}}
}

参数传递逻辑

  • epoll_wait() 会返回一个就绪事件数组

  • 每个元素 evlist_[i] 包含:

    • data.fd → 哪个 socket 触发了;

    • events → 是可读、可写还是出错;

  • Reactor 会根据这个 fd 从 users_ 查出对应对象指针(Server*Connection*),并调用 dispatcher_(fd, ev, user) 交给上层处理。

这就是事件从内核 → Reactor → Server 的传递链。

二、eventfd 与 epfd 的区别与关系

名称作用谁创建是否加入 epoll
epfd_epoll 实例,用来监听所有 fd 事件Reactor
evfd_唤醒用的“信号 fd”Reactor

evfd_ 是 Reactor 自己注册的一个 eventfd 文件描述符
当其他线程想唤醒 Reactor 时,只需要:

uint64_t one = 1;
write(evfd_, &one, 8);

这时:

  • 内核把 evfd_ 标记为“可读”;

  • epoll_wait() 立即被唤醒;

  • loop() 发现 fd == evfd_,就知道是“唤醒信号”;

  • 调用 DrainEventfd() 清空计数,继续循环。

三、为什么要清空 eventfd?

eventfd 内部维护一个 64 位计数器:

  • write() 会加一;

  • read() 会清零。

如果不 read(),它会一直被标记为“可读”,
而 epoll 在水平触发模式下会认为它永远就绪
导致 epoll_wait() 每次都立刻返回、CPU 100% 空转。

static void DrainEventfd(int evfd) {uint64_t cnt;while (read(evfd, &cnt, sizeof(cnt)) == sizeof(cnt)) {}
}

总结一句话:

如果不清空 eventfd,它的“门铃灯”会一直亮着,
epoll_wait 每次都会被立即唤醒,形成死循环。

四、epoll_wait 的逻辑本质

epoll_wait() 的作用是:

从成千上万个注册的 fd 中,筛选出当前真正有事件的那些

如果有 10 万个连接,但只有 2 个有数据:

  • epoll_wait() 只返回那 2 个;

  • 你只遍历它们,不用再检查其他 99998 个。

这一点是 epoll 能支持高并发的根本原因。

即使你写了:

for (int i = 0; i < n; ++i)

那也是在遍历“活跃 fd 列表”,不是在轮询所有连接。

五、atomic 的 store/load 与内存序

std::atomic<bool> running_{false};

store()load() 用来在线程间安全读写标志:

running_.store(true, std::memory_order_release);
while (running_.load(std::memory_order_acquire)) { ... }

含义:

  • store:写入 true,并发布写入前的所有操作;

  • load:读取 running_,确保读取的是最新的状态;

  • 多线程下保证 stop() 设置的 false 会被另一个线程立即看到。

而不是像普通 bool 那样因为 CPU cache 而“看不见更新”。

六、为什么用 vector.data()?

evlist_ 是一个 std::vector<epoll_event>

epoll_wait() 要求传入的是裸指针:

epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

所以我们用:

evlist_.data()  // 返回底层数组的首地址 (epoll_event*)

这块内存在 vector 创建时就一次性分配好了:

std::vector<epoll_event> evlist_(maxEvents);

只要不扩容(不 resize),它的地址在整个 loop 期间都是固定有效的。

七、为什么判断 if (fd == evfd_)

因为 Reactor 在 epoll 里注册了自己的 eventfd 唤醒源:

epoll_ctl(epfd_, EPOLL_CTL_ADD, evfd_, &ev);

所以 epoll_wait 可能返回两种事件:

  1. 普通 socket:说明客户端有 I/O;

  2. eventfd:说明有线程调用了 wakeup()

而:

if (fd == evfd_) {DrainEventfd(evfd_);continue;
}

这句就是在区分“网络事件”和“唤醒事件”。

八、cv.wait 的真实工作机制

std::condition_variable 是 C++ 的线程同步原语。
用法:

std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });

它做了三件事:

  1. 自动释放锁(让别的线程修改条件);

  2. 挂起当前线程(不占 CPU);

  3. 被唤醒后重新上锁,再检查条件。

唤醒不等于立刻拥有锁,多个线程被 notify_all() 唤醒后还要重新竞争锁
只有抢到锁的线程,wait 才会真正返回。

伪代码解释:

// wait 内部行为
unlock(mtx);
sleep until notified;
lock(mtx);

所以:

cv.wait() 并不会永久持锁等待,而是“放锁→睡眠→醒来再上锁”,
这样生产者才能改共享变量,否则会死锁。

九、唤醒时的锁竞争与条件检查

notify_one() 唤醒的线程会:

  1. 从内核等待队列中被唤醒;

  2. 尝试重新锁住 mutex;

  3. 拿到锁后返回 wait;

  4. 检查条件是否真的满足(可能虚假唤醒);

  5. 条件为真再继续执行。

这就是为什么标准建议用:

cv.wait(lock, []{ return 条件; });

而不是直接 cv.wait(lock)

十.小结:今天的知识体系

模块核心概念关键机制
Reactor::loopepoll_wait 分发事件从内核获取就绪 fd 并派发
eventfd唤醒机制跨线程通知 Reactor
DrainEventfd清空计数防止死循环保证下一轮 epoll 阻塞正常
atomic线程间可见性控制 loop 的安全退出
vector.data()取底层指针兼容 C 风格 epoll 接口
fd == evfd_区分唤醒与普通事件内核事件源类型判断
cv.wait条件等待解锁→睡眠→唤醒→重新加锁
notify_one唤醒机制通知等待线程竞争锁继续执行

http://www.dtcms.com/a/600915.html

相关文章:

  • 网站开发接口网站建设需要什么
  • 聚焦新“新双高计划”,高职学校如何进行数字化转型?
  • 全志V853视频输入驱动框架详解:从VIN模块到虚通道实战
  • 网站建设需要英语吗wordpress笑话主题模板
  • Azure OpenAI GPT-5 PTU 容量规划与弹性配置实践
  • [linux仓库]多线程同步:基于POSIX信号量实现生产者-消费者模型[线程·柒]
  • Linux 内核驱动加载机制
  • C语言编译软件 | 高效选择适合的C语言编译环境
  • 天津 网站策划微信、网站提成方案点做
  • 工业级部署指南:在西门子IOT2050(Debian 12)上搭建.NET 9.0环境与应用部署(进阶篇)
  • 食品网站建设网站定制开发做网站只买一个程序
  • 中小型项目前后端工时对比
  • C# 文件的输入与输出
  • Linux操作系统学习
  • idea创建javaweb项目
  • 【计网】基于OSPF 协议的局域网组建
  • 开发一个小程序花多少钱
  • Ansible入门详解
  • 一体化系统(一)智慧物业管理综合管理——东方仙盟
  • 买虚机送网站建设wordpress google ad
  • 2008 iis配置网站公司做网站需要注意些什么问题
  • vs2013编译C语言 | 探讨如何使用Visual Studio 2013进行C语言编译与调试
  • k8s上分离集群seatunnel部署(生产推荐)
  • 最新版idea2025 配置docker 打包spring-boot项目到生产服务器全流程,含期间遇到的坑
  • Python 处理 CSV 和 Excel 文件的全面指南
  • 小程序 scroll-view 触底事件不触发问题
  • word内输入带框打对号的数据
  • C语言编译器软件 | 深入了解编译过程与优化技巧
  • Spring框架 - 声明式事务管理
  • html淘宝店铺网站模板辽宁移动网站