RingBuffer模板实现SPSC无锁循环缓冲区,使用vector预分配内存、模运算/位运算处理边界、move语义避免拷贝,支持push/pop/size/capacity等操作,兼顾高性能与零内存泄漏。

用C++实现一个高性能、线程安全(可选)、无内存泄漏的循环缓冲区,核心在于正确管理读写索引、避免拷贝、利用模运算或位运算做边界处理,并支持常用操作如 push、pop、size、capacity。
基础模板实现(单生产者单消费者,无锁)
这是最常用、性能最高的场景。使用两个原子索引(或普通 int,若已保证单线程读/写),配合模运算实现环形逻辑:
templateclass RingBuffer { std::vector buffer_; size_t capacity_; size_t head_ = 0; // 下一个读取位置(消费者) size_t tail_ = 0; // 下一个写入位置(生产者) public: explicit RingBuffer(sizet capacity) : capacity(capacity), buffer_(capacity) {}
bool push(const T& item) { if (full()) return false; buffer_[tail_] = item; tail_ = (tail_ + 1) % capacity_; return true; } bool pop(T& item) { if (empty()) return false; item = std::move(buffer_[head_]); head_ = (head_ + 1) % capacity_; return true; } bool empty() const { return head_ == tail_; } bool full() const { return (tail_ + 1) % capacity_ == head_; } size_t size() const { return (tail_ >= head_) ? (tail_ - head_) : (capacity_ - head_ + tail_); } size_t capacity() const { return capacity_; }};
✅ 关键点:
• 使用 std::vector 预分配连续内存,零额外分配开销
• 模运算 实现环形索引,清晰易懂;若容量为 2 的幂,可用 tail_ & (capacity_-1) 替代模运算加速
• move 语义 在 pop 中避免冗余拷贝(尤其对大对象)
无锁优化:使用原子变量 + 内存序(SPSC 场景)
当明确只有 1 个线程 push、1 个线程 pop 时,可将 head_ 和 tail_ 改为 std::atomic_size_t,并用 relaxed 内存序提升性能:
立即学习“C++免费学习笔记(深入)”;
- push 中:用
tail_.fetch_add(1, std::memory_order_relaxed)获取旧 tail,再取模写入 - pop 中:用
head_.fetch_add(1, std::memory_order_relaxed)获取旧 head,再取模读取 - size 计算需注意:因 head/tail 异步更新,需先 load tail,再 load head,再重新 load tail 做 double-check(避免重排序导致误判)
支持多生产者/多消费者?谨慎上锁或换方案
MPMC 循环缓冲区天然复杂。强行加互斥锁(如 std::mutex)会严重拖慢吞吐。更推荐:
- 用 boost::lockfree::spsc_queue 或 moodycamel::ConcurrentQueue(工业级无锁队列)
- 若必须手写 MPMC RingBuffer:需双原子索引 + 比较交换(CAS)+ “预留槽位”协议 + ABA 保护,实现难度高、易出错
- 多数场景下,用多个 SPSC buffer + 负载分片,比单个 MPMC buffer 更快更稳
实用增强技巧(不增加复杂度)
让 RingBuffer 更好用、更健壮:
-
支持范围写入:提供
reserve()+commit(size_t n)接口,批量写入避免多次边界检查 - 自动扩容策略:仅在调试模式启用,发布版保持固定容量——环形缓冲本意就是确定性延迟
- 迭代器支持:可添加只读正向迭代器(按逻辑顺序遍历当前有效元素),方便调试和 STL 算法兼容
-
零拷贝视图:对 POD 类型,提供
data_span()返回std::span,直接暴露底层内存块
基本上就这些。环形缓冲不是越复杂越好,而是越贴合场景越高效。SPSC 下几十行模板代码就能跑满内存带宽,远胜通用容器。关键在克制——明确约束,放弃通用,换来确定性性能。










