仿muduo库实现高并发服务器—EventLoop

EventLoop 封装详解

一、整体结构

cpp 复制代码
class EventLoop {
private:
    using Functor = std::function<void()>;
    std::thread::id _thread_id;
    int _event_fd;
    std::unique_ptr<Channel> _event_channel;
    Poller _poller;
    std::vector<Functor> _tasks;
    std::mutex _mutex;
    TimerWheel _timer_wheel;

public:
    // 任务处理
    void RunAllTask();
    static int CreateEventFd();
    void ReadEventfd();
    void WeakUpEventFd();

    // 构造函数
    EventLoop();

    // 循环
    void Start();

    // 线程判断
    bool IsInLoop();
    void AssertInLoop();
    void RunInLoop(const Functor &cb);
    void QueueInLoop(const Functor &cb);

    // 转发接口
    void UpdateEvent(Channel *channel);
    void RemoveEvent(Channel *channel);
    void TimerAdd(uint64_t id, uint32_t delay, const TaskFunc &cb);
    void TimerRefresh(uint64_t id);
    void TimerCancel(uint64_t id);
    bool HasTimer(uint64_t id);
};

EventLoop 的作用:

  • 拥有一个 Poller,负责监控所有 Channel 的事件。
  • 拥有一个 TimerWheel,负责定时任务。
  • 拥有一个 eventfd 和对应的 Channel,用于唤醒自己。
  • 拥有一个任务队列,允许其他线程投递任务,保证线程安全。
  • 循环执行:等待事件 → 处理事件 → 执行任务队列

二、成员变量详解

cpp 复制代码
using Functor = std::function<void()>;
std::thread::id _thread_id;
int _event_fd;
std::unique_ptr<Channel> _event_channel;
Poller _poller;
std::vector<Functor> _tasks;
std::mutex _mutex;
TimerWheel _timer_wheel;
  • Functor:任务类型,无参数无返回值,和之前的 TaskFunc 本质一样。
  • _thread_id:保存创建该 EventLoop 时的线程 ID,用于后续判断当前线程是否为循环所在线程。
  • _event_fd:eventfd 描述符,用来唤醒可能阻塞在 epoll_wait 的循环。
  • _event_channel:包装 _event_fdChannel,当 eventfd 可读时调用回调。
  • _poller:事件监控器,内部封装了 epoll。
  • _tasks:任务队列,保存其他线程投递的函数。
  • _mutex:保护 _tasks 的互斥锁,保证多线程安全。
  • _timer_wheel:定时器模块,和 EventLoop 绑定。

三、任务处理相关私有函数

1. RunAllTask

cpp 复制代码
void RunAllTask() {
    std::vector<Functor> functor;
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        _tasks.swap(functor);
    }
    for (auto &f : functor) {
        f();
    }
    return;
}
  • 先从任务队列中"交换"出所有任务,避免长时间持有锁。
  • 然后逐个执行任务。
  • 使用 swap 而不是逐项取出,可以减少锁竞争,提高效率。

注意 :任务执行时没有锁,所以任务内部可以安全地调用 QueueInLoop 或其他接口,不会死锁。

2. CreateEventFd

cpp 复制代码
static int CreateEventFd() {
    int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
    if (efd < 0) {
        ERR_LOG("CREATE EVENTFD FAILED!!");
        abort();
    }
    return efd;
}

创建 eventfd,初始值为 0,设置 EFD_CLOEXECEFD_NONBLOCK

我们之前已经讲过 eventfd 的作用:用于跨线程唤醒。

3. ReadEventfd

cpp 复制代码
void ReadEventfd() {
    uint64_t res = 0;
    int ret = read(_event_fd, &res, sizeof(res));
    if (ret < 0) {
        if (errno == EINTR || errno == EAGAIN) {
            return;
        }
        ERR_LOG("READ EVENTFD FAILED!");
        abort();
    }
    return;
}

当 eventfd 可读时被调用,读取其中的计数,将其清零。

如果 EINTR(信号打断)或 EAGAIN(非阻塞下无数据),直接返回,不算错误。

4. WeakUpEventFd(拼写错误,应为 WakeUpEventFd)

cpp 复制代码
void WeakUpEventFd() {
    uint64_t val = 1;
    int ret = write(_event_fd, &val, sizeof(val));
    if (ret < 0) {
        if (errno == EINTR) {
            return;
        }
        ERR_LOG("READ EVENTFD FAILED!");
        abort();
    }
    return;
}

向 eventfd 写入一个 8 字节值 1,使其变为可读,从而唤醒阻塞在 epoll_wait 的循环。

这里的日志信息写成了 "READ EVENTFD FAILED!",实际上是写错误,不影响功能。


四、构造函数

cpp 复制代码
EventLoop()
    : _thread_id(std::this_thread::get_id()),
      _event_fd(CreateEventFd()),
      _event_channel(new Channel(this, _event_fd)),
      _timer_wheel(this)
{
    _event_channel->SetReadCallback(std::bind(&EventLoop::ReadEventfd, this));
    _event_channel->EnableRead();
}
  • 记录当前线程 ID,作为事件循环所属线程。
  • 创建 eventfd,用于唤醒。
  • 创建对应的 Channel,传入 this 作为所属 EventLoop。
  • 初始化 _timer_wheel,也传入 this
  • 设置 eventfd 的读回调为 ReadEventfd,并启用读事件监控。

这样,一旦其他线程向 eventfd 写入数据,epoll_wait 就会返回这个 fd 的可读事件,然后调用 ReadEventfd 读取并继续处理任务。


五、核心循环:Start

cpp 复制代码
void Start() {
    while(1) {
        // 1. 事件监控
        std::vector<Channel *> actives;
        _poller.Poll(&actives);

        // 2. 事件处理
        for (auto &channel : actives) {
            channel->HandleEvent();
        }

        // 3. 执行任务
        RunAllTask();
    }
}

这是 Reactor 模式最经典的"三步走":

  1. 事件监控 :调用 Poller::Poll,阻塞等待事件,返回活跃的 Channel* 列表。
  2. 事件处理 :遍历每个活跃 Channel,调用其 HandleEvent(),执行相应的回调。
  3. 执行任务 :执行任务队列中积累的所有函数(可能来自其他线程的 QueueInLoop)。

六、线程安全与任务投递

1. IsInLoop 和 AssertInLoop

cpp 复制代码
bool IsInLoop() {
    return (_thread_id == std::this_thread::get_id());
}

void AssertInLoop() {
    assert(_thread_id == std::this_thread::get_id());
}
  • IsInLoop:判断当前线程是否是事件循环所在线程。
  • AssertInLoop:在 debug 模式下断言必须在循环线程中调用,用于检查那些不允许跨线程调用的函数。

2. RunInLoop

cpp 复制代码
void RunInLoop(const Functor &cb) {
    if (IsInLoop()) {
        return cb();
    }
    return QueueInLoop(cb);
}
  • 如果当前线程就是事件循环线程,则立即执行 cb
  • 否则,将 cb 投递到任务队列,并唤醒事件循环。
  • 这个接口是 muduo 中非常常用的:允许任意线程安全地执行某个操作,而操作会在正确线程中运行。

3. QueueInLoop

cpp 复制代码
void QueueInLoop(const Functor &cb) {
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        _tasks.push_back(cb);
    }
    // 唤醒有可能因为没有事件就绪而导致的 epoll 阻塞
    WeakUpEventFd();
}
  • 加锁将任务放入队列,然后解锁。
  • 调用 WeakUpEventFd,唤醒阻塞在 epoll_wait 的循环,让它能够及时处理这个任务。

为什么要唤醒?

如果事件循环阻塞在 epoll_wait 上,而当前没有任何其他事件发生,那么新加入的任务就不会被执行。

通过 eventfd 写入一个值,让 epoll_wait 立即返回,从而循环能够继续执行 RunAllTask


七、接口转发

cpp 复制代码
void UpdateEvent(Channel *channel) { return _poller.UpdateEvent(channel); }
void RemoveEvent(Channel *channel) { return _poller.RemoveEvent(channel); }
void TimerAdd(uint64_t id, uint32_t delay, const TaskFunc &cb) { return _timer_wheel.TimerAdd(id, delay, cb); }
void TimerRefresh(uint64_t id) { return _timer_wheel.TimerRefresh(id); }
void TimerCancel(uint64_t id) { return _timer_wheel.TimerCancel(id); }
bool HasTimer(uint64_t id) { return _timer_wheel.HasTimer(id); }
相关推荐
_Narcissus_1 小时前
常见数论算法笔记
数据结构·c++·算法·高精度·数论·快速幂·质数筛
余额瞒着我当琳1 小时前
C++STL--list底层实现,迭代器分类,模拟list的迭代器封装、实现
java·开发语言·c++
Lucky09281 小时前
codex 安装前置软件
linux·运维·macos
今儿敲了吗1 小时前
CN——HTTP 工作流程
网络·网络协议·http
kyle~1 小时前
计算机系统 --- 缓存一致性
开发语言·c++·缓存·计算机系统
mengge.cloud1 小时前
0823Keepalived + LVS(DR) + MariaDB主主 小白完整实战教程
linux·运维·网络·oracle·负载均衡·mariadb·lvs
爱吃苹果的梨叔6 小时前
训练集群网络中断怎么办?智算机房的 Console 串口排障方案
网络·python·智能路由器·php
kk”9 小时前
Linux的组和权限管理
linux·运维·服务器
智购科技智能售货柜10 小时前
2026自动售货机整机可靠性测试:从高低温交变到EMC电磁兼容的认证工程实践~YH
运维·服务器·数据库·人工智能·物联网