从单 Reactor 线程池到 OneThreadOneLoop:高性能网络模型的演进

文章目录

  • 引言
  • [一、OneThreadOneLoop 模型的核心思想](#一、OneThreadOneLoop 模型的核心思想)
  • 二、需要新增的核心文件
    • [1. Channel.hpp:事件封装器](#1. Channel.hpp:事件封装器)
    • [2. EventLoop.hpp:线程内的事件循环](#2. EventLoop.hpp:线程内的事件循环)
    • [3. EventLoopThreadPool.hpp:事件循环线程池](#3. EventLoopThreadPool.hpp:事件循环线程池)
  • 三、需要修改的文件
    • [1. ReactorServer.hpp:主服务类](#1. ReactorServer.hpp:主服务类)
    • [2. ClientHandler.hpp:客户端连接处理器](#2. ClientHandler.hpp:客户端连接处理器)
  • 四、总结

引言

在前面的两篇文章中,我们实现了单 Reactor 单线程模型和单 Reactor 线程池模型。单 Reactor 线程池通过将 IO 操作分发到线程池处理,解决了单线程模型的性能瓶颈,但在高并发场景下,主线程的 Reactor 仍然可能成为新的瓶颈。本文将介绍如何将单 Reactor 线程池模型演进为更高效的 OneThreadOneLoop 模型,以及关键组件Channel的设计与价值。

一、OneThreadOneLoop 模型的核心思想

**OneThreadOneLoop(一个线程一个事件循环)**是高性能网络编程中的经典模型,其核心特点是:

  • 主线程(主 Reactor)仅负责监听新连接
  • 每个子线程拥有独立的事件循环(EventLoop)
  • 新连接通过轮询策略分配给子线程的 EventLoop
  • 连接的所有 IO 操作都在其绑定的子线程中处理,避免线程切换开销

相比单 Reactor 线程池,OneThreadOneLoop 的优势在于:

  1. 彻底避免多线程对同一连接的竞争
  2. 每个线程专注处理自己的事件循环,缓存友好性更好
  3. 简化线程同步逻辑,减少锁开销

二、需要新增的核心文件

1. Channel.hpp:事件封装器

Channel 是 OneThreadOneLoop 模型的关键组件,它封装了文件描述符(fd)关注的事件就绪的事件 以及对应的事件回调

cpp 复制代码
#ifndef __CHANNEL_HPP__
#define __CHANNEL_HPP__

#include <functional>
#include <sys/epoll.h>

// 事件回调类型定义
using EventCallback = std::function<void()>;

// Channel封装:负责管理单个文件描述符的IO事件
class Channel {
public:
    Channel(int fd) : _fd(fd), _events(0), _revents(0) {}
    ~Channel() = default;

    // 获取文件描述符
    int fd() const { return _fd; }
    
    // 设置/获取关注的事件
    void setEvents(uint32_t events) { _events = events; }
    uint32_t events() const { return _events; }
    
    // 设置/获取就绪的事件
    void setRevents(uint32_t revents) { _revents = revents; }
    uint32_t revents() const { return _revents; }
    
    // 设置事件回调
    void setReadCallback(EventCallback cb) { _read_cb = std::move(cb); }
    void setWriteCallback(EventCallback cb) { _write_cb = std::move(cb); }
    void setErrorCallback(EventCallback cb) { _error_cb = std::move(cb); }
    
    // 处理事件(核心方法)
    void handleEvent() {
        if (_revents & EPOLLERR) {
            if (_error_cb) _error_cb();
            return;
        }
        if (_revents & (EPOLLIN | EPOLLPRI)) {
            if (_read_cb) _read_cb();  // 触发读事件回调
        }
        if (_revents & EPOLLOUT) {
            if (_write_cb) _write_cb();  // 触发写事件回调
        }
    }
    
    // 事件操作辅助函数
    void enableReading() { _events |= EPOLLIN; }
    void enableWriting() { _events |= EPOLLOUT; }
    void disableWriting() { _events &= ~EPOLLOUT; }
    void disableAll() { _events = 0; }
    bool isWriting() const { return _events & EPOLLOUT; }

private:
    int _fd;                  // 关联的文件描述符
    uint32_t _events;         // 关注的事件(如EPOLLIN、EPOLLOUT)
    uint32_t _revents;        // 就绪的事件(由epoll返回)
    EventCallback _read_cb;   // 读事件回调
    EventCallback _write_cb;  // 写事件回调
    EventCallback _error_cb;  // 错误事件回调
};

#endif /* __CHANNEL_HPP__ */

Channel 的核心功能

  • 作为 fd 与事件循环的中间层,隔离了底层 IO 复用(如 epoll)的细节
  • 将事件(读 / 写 / 错误)与对应的处理逻辑(回调函数)绑定
  • 提供简洁的接口控制关注的事件(如启用 / 禁用读 / 写事件)

为什么说 Channel 并非必须,但却是最佳实践?

  • 理论上,我们可以直接在 EventLoop 中处理 fd 和事件回调,但会导致代码耦合严重
  • Channel 通过封装,实现了 "事件 - 回调" 的解耦,让 EventLoop 只需负责事件分发,无需关心具体业务
  • 在工程实践中,Channel 大幅提升了代码的可维护性和扩展性(例如新增事件类型时只需修改 Channel)

2. EventLoop.hpp:线程内的事件循环

每个线程拥有一个 EventLoop,负责监听该线程上所有 Channel 的事件,并分发处理。

cpp 复制代码
#ifndef __EVENT_LOOP_HPP__
#define __EVENT_LOOP_HPP__

#include "EpollDemultiplexer.hpp"
#include "Channel.hpp"
#include <map>
#include <thread>
#include <mutex>
#include <vector>

// 事件循环类:每个线程一个EventLoop
class EventLoop {
public:
    EventLoop() : _demux(), _thread_id(std::this_thread::get_id()), _running(false) {}
    ~EventLoop() { stop(); }

    // 禁用拷贝
    EventLoop(const EventLoop&) = delete;
    EventLoop& operator=(const EventLoop&) = delete;

    // 启动事件循环
    void start() {
        _running = true;
        loop();
    }

    // 停止事件循环
    void stop() {
        _running = false;
    }

    // 判断当前线程是否是事件循环线程(线程安全检查)
    bool isInLoopThread() const {
        return std::this_thread::get_id() == _thread_id;
    }

    // 注册Channel(将fd和事件注册到epoll)
    void updateChannel(Channel* channel) {
        int fd = channel->fd();
        if (_channels.find(fd) == _channels.end()) {
            _channels[fd] = channel;
            _demux.addEvent(fd, channel->events());
        } else {
            _demux.modifyEvent(fd, channel->events());
        }
    }

    // 移除Channel
    void removeChannel(Channel* channel) {
        int fd = channel->fd();
        if (_channels.find(fd) != _channels.end()) {
            _demux.removeEvent(fd);
            _channels.erase(fd);
        }
    }

private:
    // 事件循环主体
    void loop() {
        std::vector<epoll_event> events(1024);
        while (_running) {
            // 等待事件就绪
            int ready = _demux.waitEvents(events, 1000);
            if (ready < 0) continue;

            // 分发事件到对应Channel
            for (int i = 0; i < ready; ++i) {
                int fd = events[i].data.fd;
                auto it = _channels.find(fd);
                if (it != _channels.end()) {
                    it->second->setRevents(events[i].events);  // 设置就绪事件
                    it->second->handleEvent();  // 触发Channel的事件处理
                }
            }
        }
    }

private:
    EpollDemultiplexer _demux;                // 多路复用器(封装epoll)
    std::map<int, Channel*> _channels;        // fd到Channel的映射
    std::thread::id _thread_id;               // 事件循环所在线程ID
    bool _running;                            // 运行状态
};

#endif /* __EVENT_LOOP_HPP__ */

3. EventLoopThreadPool.hpp:事件循环线程池

管理多个 EventLoop 线程,负责线程的创建和轮询分配新连接。

cpp 复制代码
#ifndef __EVENT_LOOP_THREAD_POOL_HPP__
#define __EVENT_LOOP_THREAD_POOL_HPP__

#include "EventLoop.hpp"
#include <vector>
#include <memory>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>

// 事件循环线程
class EventLoopThread {
public:
    EventLoopThread() : _loop(nullptr), _running(false) {}
    
    ~EventLoopThread() {
        if (_running) {
            _running = false;
            if (_thread.joinable()) {
                _thread.join();
            }
        }
    }

    // 启动线程并返回事件循环
    EventLoop* startLoop() {
        _running = true;
        _thread = std::thread(&EventLoopThread::threadFunc, this);
        
        // 等待事件循环初始化完成
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock, [this]() { return _loop != nullptr; });
        
        return _loop;
    }

private:
    // 线程函数:运行事件循环
    void threadFunc() {
        EventLoop loop;  // 线程局部的事件循环
        
        {
            std::lock_guard<std::mutex> lock(_mutex);
            _loop = &loop;
            _cond.notify_one();  // 通知主线程循环已初始化
        }
        
        loop.start();  // 进入事件循环
        _loop = nullptr;
    }

private:
    EventLoop* _loop;                  // 事件循环
    bool _running;                     // 运行状态
    std::thread _thread;               // 线程
    std::mutex _mutex;                 // 互斥锁
    std::condition_variable _cond;     // 条件变量
};

// 事件循环线程池
class EventLoopThreadPool {
public:
    EventLoopThreadPool(size_t numThreads) : _num_threads(numThreads), _next_loop(0) {}
    
    // 启动线程池
    void start() {
        for (size_t i = 0; i < _num_threads; ++i) {
            auto thread = std::make_unique<EventLoopThread>();
            _loops.push_back(thread->startLoop());  // 保存每个线程的EventLoop
            _threads.push_back(std::move(thread));
        }
    }
    
    // 轮询获取下一个事件循环(负载均衡)
    EventLoop* getNextLoop() {
        if (_loops.empty()) return nullptr;
        
        EventLoop* loop = _loops[_next_loop];
        _next_loop = (_next_loop + 1) % _loops.size();  // 轮询索引
        return loop;
    }

private:
    size_t _num_threads;                          // 线程数量
    std::vector<EventLoop*> _loops;               // 事件循环列表
    std::vector<std::unique_ptr<EventLoopThread>> _threads;  // 线程列表
    size_t _next_loop;                            // 下一个要使用的事件循环索引
};

#endif /* __EVENT_LOOP_THREAD_POOL_HPP__ */

三、需要修改的文件

1. ReactorServer.hpp:主服务类

主要修改点:

  • 引入 EventLoopThreadPool 管理子线程的事件循环
  • 使用 Channel 管理监听套接字的事件(AcceptorChannel)
  • 新连接通过线程池分配到子线程的 EventLoop
cpp 复制代码
#ifndef __REACTOR_SERVER_HPP__
#define __REACTOR_SERVER_HPP__

#include "EpollDemultiplexer.hpp"
#include "ClientHandler.hpp"
#include "Socket.hpp"
#include "EventLoop.hpp"
#include "EventLoopThreadPool.hpp"
#include "Channel.hpp"
#include <map>
#include <memory>
#include <cstdint>
#include <atomic>

class ReactorServer {
public:
    ReactorServer(uint16_t port, int backlog = 1024, size_t thread_num = 4) 
        : _port(port), _backlog(backlog), _main_loop(),
          _thread_pool(thread_num), _is_running(false),
          _listen_socket(std::make_unique<TcpSocket>()) {}
          
    ~ReactorServer() {
        Stop();
    }

    // 初始化服务器
    bool Init() {
        if (_is_running) {
            std::cerr << "服务器已在运行中" << std::endl;
            return false;
        }

        try {
            // 创建并配置监听Socket
            _listen_socket->SocketOrDie();
            // 设置端口复用
            int opt = 1;
            setsockopt(_listen_socket->Fd(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
            _listen_socket->BindOrDie(_port);
            _listen_socket->ListenOrDie(_backlog);
            _listen_socket->SetNonBlocking();
            
            // 初始化Acceptor的Channel(核心修改)
            initAcceptor();
            
            // 启动事件循环线程池
            _thread_pool.start();
            
            return true;
        } catch (...) {
            std::cerr << "服务器初始化失败" << std::endl;
            return false;
        }
    }

    // 启动服务器
    bool Start() {
        if (_is_running) return false;
        _is_running = true;
        std::cout << "服务器启动成功,监听端口 " << _port << "..." << std::endl;
        _main_loop.start();  // 启动主事件循环
        return true;
    }

    // 停止服务器
    void Stop() {
        if (!_is_running) return;
        _is_running = false;
        _main_loop.stop();
        _clients.clear();
        _acceptor_channel.reset();
        _listen_socket->Close();
    }

private:
    // 初始化Acceptor的Channel
    void initAcceptor() {
        int listen_fd = _listen_socket->Fd();
        _acceptor_channel = std::make_unique<Channel>(listen_fd);
        
        // 设置读事件回调:接收新连接
        _acceptor_channel->setReadCallback([this]() {
            std::string client_ip;
            int client_fd = _listen_socket->Accept(&client_ip);
            if (client_fd >= 0) {
                onNewConnection(client_fd, client_ip);
            }
        });
        
        // 关注读事件
        _acceptor_channel->enableReading();
        _main_loop.updateChannel(_acceptor_channel.get());  // 注册到主循环
    }

    // 处理新连接(核心修改:分配到子线程的EventLoop)
    void onNewConnection(int client_fd, const std::string& client_ip) {
        std::cout << "新连接:IP=" << client_ip << ", fd=" << client_fd << std::endl;

        // 选择一个子事件循环
        EventLoop* loop = _thread_pool.getNextLoop();
        if (!loop) {
            close(client_fd);
            return;
        }

        // 创建客户端处理器并绑定到选中的事件循环
        auto client_handler = std::make_shared<ClientHandler>(
            client_fd, *loop,
            [this, client_ip](int fd, const std::string& data) {
                onMessage(fd, client_ip, data);
            },
            [this](int fd) {
                onClose(fd);
            });

        _clients[client_fd] = client_handler;
    }

    // 处理客户端消息
    void onMessage(int fd, const std::string& client_ip, const std::string& data) {
        std::cout << "[" << client_ip << "][fd=" << fd << "] 收到数据: " << data << std::endl;
        auto it = _clients.find(fd);
        if (it != _clients.end()) {
            it->second->sendData("服务器回显: " + data);
        }
    }

    // 处理客户端关闭
    void onClose(int fd) {
        std::cout << "客户端断开:fd=" << fd << std::endl;
        _clients.erase(fd);
    }

private:
    uint16_t _port;                          // 监听端口
    int _backlog;                            // 监听队列大小
    EventLoop _main_loop;                    // 主事件循环(处理新连接)
    EventLoopThreadPool _thread_pool;        // 事件循环线程池
    std::atomic<bool> _is_running;           // 运行状态标识
    std::unique_ptr<TcpSocket> _listen_socket;  // 监听Socket
    std::unique_ptr<Channel> _acceptor_channel; // Acceptor的Channel
    std::map<int, std::shared_ptr<ClientHandler>> _clients;  // 客户端处理器映射
};

#endif /* __REACTOR_SERVER_HPP__ */

2. ClientHandler.hpp:客户端连接处理器

主要修改点:

  • 使用 Channel 管理客户端 fd 的读写事件
  • 将 IO 事件绑定到所属子线程的 EventLoop
cpp 复制代码
#ifndef __CLIENT_HANDLER_HPP__
#define __CLIENT_HANDLER_HPP__

#include "EventHandler.hpp"
#include "EventLoop.hpp"
#include "Channel.hpp"
#include "Socket.hpp"
#include <functional>
#include <string>
#include <mutex>

// 客户端处理器:处理数据读写事件
class ClientHandler : public EventHandler {
public:
    using Ptr = std::shared_ptr<ClientHandler>;
    using MessageCallback = std::function<void(int, const std::string&)>;
    using CloseCallback = std::function<void(int)>;

    ClientHandler(int client_fd, EventLoop& loop, MessageCallback msg_cb, CloseCallback close_cb)
        : EventHandler(client_fd), _loop(loop), _msg_cb(std::move(msg_cb)),
          _close_cb(std::move(close_cb)), _client_socket(client_fd), _channel(client_fd) {
        // 初始化Channel回调(核心修改)
        _channel.setReadCallback([this]() { handleRead(); });
        _channel.setWriteCallback([this]() { handleWrite(); });
        _channel.setErrorCallback([this]() { handleError(); });
        
        // 开始关注读事件
        _channel.enableReading();
        _loop.updateChannel(&_channel);  // 注册到子线程的EventLoop
    }

    ~ClientHandler() {
        _loop.removeChannel(&_channel);  // 从事件循环中移除
    }

    // 处理读事件
    void handleRead() override {
        std::string data;
        ssize_t n = _client_socket.Recv(&data);

        if (n < 0) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                _close_cb(_fd);
            }
            return;
        }
        
        if (n == 0) {  // 客户端断开
            _close_cb(_fd);
            return;
        }
        
        _msg_cb(_fd, data);  // 触发消息回调
    }

    // 处理写事件(使用缓冲区实现异步发送)
    void handleWrite() override {
        std::lock_guard<std::mutex> lock(_buf_mutex);
        if (_send_buf.empty()) {
            _channel.disableWriting();  // 无数据可写,取消关注
            _loop.updateChannel(&_channel);
            return;
        }

        // 发送数据
        ssize_t n = _client_socket.Send(_send_buf);
        if (n > 0) {
            _send_buf.erase(0, n);
        }

        // 剩余数据继续监听可写事件
        if (!_send_buf.empty()) {
            _channel.enableWriting();
        } else {
            _channel.disableWriting();
        }
        _loop.updateChannel(&_channel);
    }

    void handleError() override {
        _close_cb(_fd);
    }

    // 发送数据(线程安全)
    void sendData(const std::string& data) {
        bool need_wakeup = false;
        {
            std::lock_guard<std::mutex> lock(_buf_mutex);
            need_wakeup = _send_buf.empty();  // 缓冲区为空时需要唤醒写事件
            _send_buf += data;
        }
        
        if (need_wakeup) {
            _channel.enableWriting();
            _loop.updateChannel(&_channel);
        }
    }

private:
    EventLoop& _loop;                // 所属的事件循环(子线程)
    MessageCallback _msg_cb;         // 消息处理回调
    CloseCallback _close_cb;         // 关闭回调
    std::string _send_buf;           // 待发送数据缓冲区
    std::mutex _buf_mutex;           // 缓冲区互斥锁
    TcpSocket _client_socket;        // 客户端Socket封装
    Channel _channel;                // 客户端Channel(核心组件)
};

#endif /* __CLIENT_HANDLER_HPP__ */

四、总结

从单 Reactor 线程池到 OneThreadOneLoop 的演进,核心变化是:

  1. 引入Channel组件,实现事件与回调的解耦
  2. 每个子线程拥有独立的EventLoop,负责本线程的事件循环
  3. 新连接通过线程池的轮询策略分配给子线程,实现负载均衡

这种模型在高并发场景下性能优势明显,也是许多高性能网络库(如 libevent、muduo)的核心设计。Channel 虽然不是实现事件驱动的必需组件,但它通过封装细节、解耦代码,成为了工程实践中的最佳选择。

相关推荐
AI智域边界 - Alvin Cho2 小时前
Bloomberg、LSEG 与 MCP 缺口:为什么尚未发布完整的 MCP 服务器,以及多智能体系统如何解決这问题
运维·服务器
还下着雨ZG2 小时前
TCP/IP协议族详细介绍
网络·网络协议·tcp/ip·计算机网络
国服第二切图仔3 小时前
Rust开发之Trait 定义通用行为——实现形状面积计算系统
开发语言·网络·rust
蒙奇D索大3 小时前
【计算机网络】[特殊字符] 408高频考点 | 数据链路层组帧:从字符计数到违规编码,一文学透四大实现方法
网络·笔记·学习·计算机网络·考研
_OP_CHEN3 小时前
Linux网络编程:(七)Vim 编辑器完全指南:从入门到精通的全方位实战教程
linux·运维·服务器·编辑器·vim·linux生态·linux软件
Maple_land3 小时前
第1篇:Linux工具复盘上篇:yum与vim
linux·运维·服务器·c++·centos
奋斗的牛马4 小时前
OFDM理解
网络·数据库·单片机·嵌入式硬件·fpga开发·信息与通信
忧郁的橙子.4 小时前
一、Rabbit MQ 初级
服务器·网络·数据库
liao__ran5 小时前
ClickHouse CPU 排查快速参考指南
运维·服务器·clickhouse