拆开 Hical:Vyukov MPSC 无锁队列在 HTTP 服务器上的实战——GenericConnection 写路径

[Hical] Vyukov MPSC 无锁队列在 HTTP 服务器上的实战:GenericConnection 的写路径 本专栏文章:拆开 Hical · 第 4 篇 前面三篇都在 HTTP 层面打转——请求怎么解析、路由怎么匹配、中间件怎么执行。这一篇沉到网络层,看一个具体的问题:多个协程想往同一个 socket 写数据时,怎么不加锁? 答案藏在 GenericConnection 的 Vyukov MPSC 无锁队列里。 1. 问题:多个协程同时往一个连接上写 先搞清楚为什么会有这个问题。HTTP/2 和 WebSocket 都允许在一个 TCP 连接上并发地处理多个"流": 1 2 3 线程 A(协程处理 WebSocket frame)──→ 想往 socket 写数据 线程 B(协程处理心跳 ping) ──→ 也想往 socket 写数据 线程 C(IO 线程正在写上一批数据) ──→ socket 只能同时一个写操作 传统的做法是 std::mutex + std::queue。但这里有三个痛点: 生产者多、消费者一个:多个协程往队列里塞数据,只有一个写协程取出来发给 socket mutex 竞争:每秒几十万次 send → 几十万次 mutex lock/unlock → 内核态的 futex 开销 队列长度短:大多数时候队列深度 < 5,争锁的开销比实际写数据还大 2. Vyukov MPSC 队列:核心原理 Dmitry Vyukov 的 MPSC 队列专门为"多生产者、单消费者"场景设计。先直观理解——想象排队买票: ...

August 10, 2026 · 5 min · 984 words

并发工具 — MpscQueue 无锁队列 & ObjectPool 对象池

第17 课:并发工具 — MpscQueue 无锁队列 & ObjectPool 对象池 对应源文件: trantor/utils/LockFreeQueue.h — MpscQueue<T>:无锁多生产者单消费者队列 trantor/utils/ObjectPool.h — ObjectPool<T>:共享指针驱动的对象池 一、为什么需要这两个工具? 在高性能网络库中,有两类性能瓶颈反复出现: 瓶颈 1:跨线程任务投递 EventLoop::runInLoop() 每秒可能被调用数十万次(每个连接的读写完成都要回调)。如果用 mutex 保护投递队列,大量线程争锁会造成显著延迟。 → 解决方案:MpscQueue<T> 无锁队列,多线程投递不需要 mutex。 瓶颈 2:高频内存分配 HTTP 框架每个请求都要 new HttpRequest、new HttpResponse,请求完成立刻 delete。频繁的堆分配/释放不仅慢,还会造成内存碎片。 → 解决方案:ObjectPool<T> 对象池,释放时归还而不销毁,下次直接复用。 二、MpscQueue — 无锁多生产者单消费者队列 2.1 完整实现(共 ~115 行) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <typename T> class MpscQueue : public NonCopyable { private: struct BufferNode { BufferNode() = default; BufferNode(const T &data) : dataPtr_(new T(data)) {} BufferNode(T &&data) : dataPtr_(new T(std::move(data))) {} T *dataPtr_; // 堆上的数据 std::atomic<BufferNode *> next_{nullptr}; }; std::atomic<BufferNode *> head_; // 指向最新插入的节点 std::atomic<BufferNode *> tail_; // 指向最旧的哨兵节点(消费端) }; 2.2 初始状态 1 2 3 4 MpscQueue() : head_(new BufferNode), // 创建哨兵节点 tail_(head_.load(std::memory_order_relaxed)) // tail = head = 哨兵 {} 初始状态: ...

April 10, 2025 · 10 min · 1947 words