muduo网络库(八):EventLoop 事件循环
muduo网络库八EventLoop 事件循环muduo网络库八EventLoop 事件循环概述EventLoop 的内部结构loop 主循环跨线程唤醒机制为什么需要 wakeup跨线程投递流程doPendingFunctors 的巧妙设计One Loop Per Thread 多线程模型mainLoop 与 subLoops 的分工多线程协作时间线wakeup 如何融入流程单个 EventLoop 的生命周期muduo网络库八EventLoop 事件循环概述EventLoop相当于 muduo 的主反应堆其中包含了Channel类和Poller类。muduo 采用经典的One Loop Per Thread模型——每个线程拥有一个 EventLoop负责该线程内所有文件描述符的事件监听和回调分发。EventLoop 的核心职责可以概括为两点IO 事件分发通过 Poller 监听 fd事件就绪后调用 Channel 的回调跨线程任务执行通过 wakeupFd 唤醒机制让其他线程安全地投递任务到本线程执行EventLoop 的内部结构EventLoop ├── poller_ (EpollPoller) │ ├── epollFd_ ← epoll 实例 │ └── channels_ ← 所有注册的 fd │ ├── listenfd → Channel (主线程) │ ├── connfd1 → Channel (工作线程) │ ├── connfd2 → Channel (工作线程) │ └── ... ├── wakeupFd_ ← 跨线程唤醒用 └── wakeupChannel_ ← 封装 wakeupFd_除了 Poller 管理的 IO 通道外EventLoop 还有一个特殊的wakeupFd_——这是eventfd创建的文件描述符专门用于跨线程唤醒。当其他线程需要让当前 EventLoop 立即处理某个任务时就往这个 fd 写入数据让epoll_wait立刻返回。loop 主循环loop()是 EventLoop 的核心函数也是整个事件驱动引擎的心脏。它的执行流程如下┌──────────────────────────────────────────────────────────────────┐ │ EventLoop::loop() 主循环 │ ├──────────────────────────────────────────────────────────────────┤ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 1. EpollPoller::poll() → epoll_wait() 阻塞等待事件 │ │ │ └───────────────────────────┬────────────────────────────────┘ │ │ ↓ 有事件发生或被wakeup唤醒 │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 2. fillActiveChannels() 收集活跃的 Channel │ │ │ └───────────────────────────┬────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 3. 遍历 activeChannels → Channel::handleEvent() │ │ │ │ ↓ │ │ │ │ 调用各回调readCallback_/writeCallback_/closeCallback_...│ │ │ └────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 4. doPendingFunctors() 执行其他线程投递的任务 │ │ │ └────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────┘其中channel-handleEvent()负责处理 epoll 返回的 IO 事件而doPendingFunctors()负责处理其他线程投递的任务。关键细节EventLoop 在构造函数中已经将 wakeupFd 的读回调绑定到handleRead函数所以当handleEvent()处理 wakeup 事件时实际上只是消费唤醒信号读取 8 字节数据真正的任务在pendingFunctors_队列中等待执行。跨线程唤醒机制为什么需要 wakeup假设线程 B 的 EventLoop 正在epoll_wait()中阻塞等待事件此时线程 A 想让它执行一个任务比如注册新的 Channel。如果不做特殊处理线程 B 根本不知道有任务到来只能等下一个 IO 事件到来才会醒来——这可能要等很久。wakeupFd_就是为了解决这个问题线程 A 往wakeupFd_写入数据epoll 立刻检测到可读事件线程 B 从epoll_wait()返回然后执行doPendingFunctors()处理任务。跨线程投递流程线程A 线程B (EventLoop线程) ┌─────────────────────────┐ ┌─────────────────┐ │ 线程池中获取subLoop │ │ │ │ runInLoop(func) │ │ poll() 阻塞等待 │ │ └─ isInLoopThread() → false │ │ │ └─ queueInLoop(func) │ │ │ ├─ func → queue │ │ │ └─ wakeup() │ │ │ │ │ │ │ ▼ │ │ │ write(wakeupFd) ──────────▶ poll()返回│ │ │ │ │ │ │ │ handleEvent(wakeupChannel)│ │ │ │ └─ read(wakeupFd) 消费信号│ │ │ │ │ │ │ │ doPendingFunctors() │ │ │ │ └─ 执行 task │ │ │ │ │ └─────────────────────────┘ └─────────────────┘当线程 A 向线程 B 投递任务时先将任务放入pendingFunctors_队列加锁保护再调用wakeup()往wakeupFd_写入数据线程 B 的epoll_wait()检测到 wakeupFd 可读立即返回执行doPendingFunctors()依次处理队列中的任务wakeup 的作用只是让 epoll_wait 立即返回任务早就投入队列了。就算不立即唤醒下次有 IO 事件时也会调用doPendingFunctors()处理这些任务。但为了实时性通常需要立即唤醒。这种设计还有一个优势当批量投递多个任务时只需一次唤醒doPendingFunctors()会一次性处理所有积攒的任务。doPendingFunctors 的巧妙设计doPendingFunctors()不是直接遍历pendingFunctors_执行而是先将其交换到一个局部变量中voiddoPendingFunctors(){std::vectorFunctorfunctors;{MutexLockGuardlock(mutex_);functors.swap(pendingFunctors_);// 交换而非拷贝}for(constFunctorfn:functors){fn();// 在锁外执行}}这个swap设计非常精妙缩小临界区只在 swap 时加锁执行任务时不需要持锁减少锁竞争避免死锁任务内部可能再次调用runInLoop()如果执行时仍持锁就会死锁提高吞吐新投递的任务在执行期间可以继续加入队列下一轮循环再处理One Loop Per Thread 多线程模型mainLoop 与 subLoops 的分工muduo 的多线程模型遵循One Loop Per Thread原则mainLoop主线程的 EventLoop负责accept 新连接subLoopsN 个子线程的 EventLoop负责处理已连接的 IO 读写它们之间不靠消息队列、不靠管道、不靠复杂通信只靠wakeup() 锁 pendingFunctors_完成全部交互。多线程协作时间线时间线 → 主线程 mainLoop: poll() → 检测到 listenfd 可读 → accept() → 获取 connfd ↓ 将 connfd 封装为 Channel通过 queueInLoop() 发送到工作线程 ↓ 继续 poll() 等待下一个事件 工作线程 subLoop1: poll() 阻塞中 ←── 被 wakeupFd 唤醒 ↓ 执行 pendingFunctors 中的任务注册新 Channel ↓ poll() 继续监听 connfd 事件 ↓ 检测到 connfd 可读 → 调用 Channel 的 readCallback_wakeup 如何融入流程mainLoop 拿到新连接主线程accept到新的客户端连接mainLoop 把任务发给 subLoopsubLoop-runInLoop(把新连接交给你处理);runInLoop 内部做两件事加锁 → 将任务放入pendingFunctors_→wakeup()唤醒 subLoopsubLoop 被唤醒后从doPendingFunctors()中取出函数swap 后依次执行wakeup 的本质往 subLoop 自己的 eventfd 写一个 8 字节数据让 subLoop 从epoll_wait()阻塞中立刻醒来。单个 EventLoop 的生命周期从 Channel 注册到事件处理的完整流程用户注册事件 ↓ Channel::enableXxx() → Channel::update() ↓ EventLoop::updateChannel() ← 新 channel 加入 channels_ Map ↓ EpollPoller::update() ↓ 根据 channel 的 index 判断是添加还是删除 epoll_ctl(ADD/MOD/DEL) ← 内核注册 注册完成后EventLoop 的 loop() 循环就会持续监听这些 fd一旦事件就绪就触发回调。 ## 设计精髓总结 | 设计点 | 做法 | 收益 | |--------|------|------| | One Loop Per Thread | 每线程一个 EventLoop | 线程内无需加锁避免竞争 | | wakeupFd 唤醒 | eventfd epoll 监听 | 跨线程通信低延迟立即可达 | | pendingFunctors | 任务队列 swap | 临界区最小化避免死锁 | | 事件驱动 | poll → handleEvent → doPendingFunctors | IO 与任务统一在事件循环中处理 | | mainLoop/subLoops | 主线程 accept子线程处理 IO | 新连接分发均衡扩展性强 | EventLoop 是 muduo 网络库的灵魂它将 Channel事件通道、PollerIO 复用、wakeupFd跨线程唤醒和 pendingFunctors任务队列有机整合构建了一个高效、可扩展、线程安全的事件驱动引擎。

相关新闻

实战视角下的量化系统构建:从零到专业级交易平台的进阶指南

实战视角下的量化系统构建:从零到专业级交易平台的进阶指南

实战视角下的量化系统构建:从零到专业级交易平台的进阶指南 【免费下载链接】zvt modular quant framework. 项目地址: https://gitcode.com/gh_mirrors/zv/zvt ZVT是一个模块化量化交易框架,专为量化爱好者和专业交易者设计,提供从数…

2026/10/16 22:16:27 阅读更多 →
IDM激活脚本终极指南:Windows下载工具永久免费解决方案

IDM激活脚本终极指南:Windows下载工具永久免费解决方案

IDM激活脚本终极指南:Windows下载工具永久免费解决方案 【免费下载链接】IDM-Activation-Script IDM Activation & Trail Reset Script 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script 还在为Internet Download Manager的30天试用期…

2026/10/16 0:47:00 阅读更多 →
为什么使用 Docker 镜像

为什么使用 Docker 镜像

mylab 版本 v1.0 | 2026-07-22 适用读者:想搞懂「镜像到底是干嘛的、为什么不直接在 ECS 上装 Node 跑」的部署新手 一、先说结论 对于 mylab 这样的 Next.js 站点,「把应用打包成 Docker 镜像、在容器里运行」并不是为了炫技,而是为了解决一…

2026/10/16 5:08:29 阅读更多 →

最新新闻

MT7663 USB WiFi驱动移植指南:交叉编译、固件加载与wlan0注册

MT7663 USB WiFi驱动移植指南:交叉编译、固件加载与wlan0注册

简介:这份资源是MT7663 USB转WiFi驱动源码包,面向需要在嵌入式Linux平台上移植无线网卡的开发者,尤其适合海思3531等ARM64平台。源码已在海思3531上交叉编译通过,生成的wlan_mt7663_usb.ko约3MB,配套的Makefile.aarch已…

2026/10/18 14:18:27 阅读更多 →
ADC数据手册深度解析:从PDF参数提取到硬件时序实现

ADC数据手册深度解析:从PDF参数提取到硬件时序实现

简介:本资源为MSM381A3729Z9BM1C模拟输出MEMS麦克风的官方英文数据手册(PDF),面向电子工程师、硬件开发人员及声学器件选型技术人员,用于指导该高性能微型麦克风在移动终端与可穿戴设备中的电路设计、PCB布局、SMT贴装…

2026/10/18 14:18:27 阅读更多 →
P1268 树的重量 【洛谷算法习题】

P1268 树的重量 【洛谷算法习题】

P1268 树的重量 网页链接 P1268 树的重量 题目描述 树可以用来表示物种之间的进化关系。一棵“进化树”是一个带边权的树,其叶节点表示一个物种,两个叶节点之间的距离表示两个物种的差异。现在,一个重要的问题是,根据物种之间…

2026/10/18 14:18:27 阅读更多 →
GRBL固件深度解析:从Arduino到工业级CNC运动控制

GRBL固件深度解析:从Arduino到工业级CNC运动控制

简介:本资源是面向嵌入式开发者、DIY数控爱好者及高校机电类专业学生的GRBL开源固件完整源码包,专为基于Arduino的CNC控制器开发与学习设计,解决小型数控设备(如雕刻机、激光切割机)的G代码解析与运动控制核心问题。压…

2026/10/18 14:18:27 阅读更多 →
PAD竞速答题怎么按队伍分组上屏:一场厂区开幕式安全知识答题的系统配置复盘

PAD竞速答题怎么按队伍分组上屏:一场厂区开幕式安全知识答题的系统配置复盘

本文范围:开幕式里的答题环节;PAD型号、组网方式和系统代码不在讨论内。1. 项目背景塔河炼化(中国石化塔河炼化有限责任公司)2026年5月26日在新疆阿克苏地区举行第二届“最强操作”竞赛开幕式,开幕式里安排了一段安全知…

2026/10/18 14:18:27 阅读更多 →
超立方体Ramsey(openai/math 家族171)攻坚日志 H11:row-link商图构造长链路形式化进展(节选)

超立方体Ramsey(openai/math 家族171)攻坚日志 H11:row-link商图构造长链路形式化进展(节选)

超立方体Ramsey(openai/math 家族171)攻坚日志 H11:row-link商图构造长链路形式化进展(节选)专栏:组合概率与形式化审计|标签:Lea(n^4)、极值组合、Ramsey理论、条件局部引理、超立方…

2026/10/18 14:17:26 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/18 12:16:16 阅读更多 →