EventLoop — 事件循环

第 5 课:EventLoop — 事件循环 对应源文件: trantor/net/EventLoop.h — 公共接口 trantor/net/EventLoop.cc — 实现 trantor/utils/LockFreeQueue.h — 无锁任务队列(MpscQueue) 一、EventLoop 是什么? EventLoop 是 trantor 整个框架的心脏,也是 Reactor 模式的核心。 一句话定义:一个线程,一个循环,监听所有 I/O 事件和定时器,串行处理所有回调。 1 2 3 4 5 6 7 8 9 ┌─────────────────────────────────────────┐ │ EventLoop::loop() │ │ │ │ while (!quit_) { │ │ ① poller_->poll(timeout) ←阻塞等事件│ │ ② 处理所有就绪的 Channel 回调 │ │ ③ doRunInLoopFuncs() ← 执行投递任务 │ │ } │ └─────────────────────────────────────────┘ 三个核心原则: One loop per thread:一个 EventLoop 只属于一个线程,且一个线程最多一个 EventLoop 所有 I/O 操作在 Loop 线程执行:不跨线程操作 socket 跨线程操作必须通过 runInLoop/queueInLoop:把任务投递进去,由 Loop 线程执行 二、核心主循环 loop() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // EventLoop.cc 第 204-266 行(精简版) void EventLoop::loop() { assert(!looping_); assertInLoopThread(); // 必须在 Loop 线程调用 looping_.store(true, std::memory_order_release); auto loopFlagCleaner = makeScopeExit( // RAII:确保退出时清 looping_ [this]() { looping_.store(false, ...); }); while (!quit_.load(std::memory_order_acquire)) { activeChannels_.clear(); // ① 阻塞等待 I/O 事件(最长 10 秒) #ifdef __linux__ poller_->poll(kPollTimeMs, &activeChannels_); // Linux: epoll #else poller_->poll(timerQueue_->getTimeout(), &activeChannels_); timerQueue_->processTimers(); // 非 Linux: 手动处理定时器 #endif // ② 处理所有就绪 Channel 的回调 eventHandling_ = true; for (auto *channel : activeChannels_) { currentActiveChannel_ = channel; channel->handleEvent(); // 分发读/写/错误回调 } currentActiveChannel_ = nullptr; eventHandling_ = false; // ③ 执行跨线程投递进来的任务 doRunInLoopFuncs(); } // 退出后执行 runOnQuit 注册的清理函数 Func f; while (funcsOnQuit_.dequeue(f)) f(); } 主循环的三个阶段 1 2 3 4 5 6 7 8 9 10 11 12 Phase 1: poller_->poll() └─ 调用 epoll_wait(最多等 10 秒) └─ 返回:活跃的 Channel 列表(有读/写事件的 fd) Phase 2: handleEvent() └─ 遍历活跃 Channel,各自回调 └─ 例如:socket 可读 → RecvMessageCallback socket 可写 → WriteCompleteCallback Phase 3: doRunInLoopFuncs() └─ 消费 funcs_ 队列(MpscQueue),执行所有投递进来的任务 └─ 例如:其他线程调用了 queueInLoop(f) 三、wakeupFd:打破阻塞的关键 问题:epoll_wait 在等待时是阻塞的,如果其他线程此时投递了一个任务(queueInLoop),Loop 线程要等最多 10 秒才能执行。 ...

March 10, 2025 · 9 min · 1848 words