当前位置: 首页 > news >正文

Ringbuffer 无锁环形队列

RingBuffer(环形缓冲区):

是一种使用固定大小数组实现的(SPSC)单生产者单消费者、先进先出(FIFO)数据结构。它在逻辑上首尾相连,形成一个环形,可以循环使用内存空间。

RingBuffer 的核心特性

1.固定大小,容量为2的幂。

2.循环利用,写满后从头开始写入、覆盖旧数据(或拒绝写入)。

3.高效的索引计算,使用位运算取模

4.无锁并发:SPSC(单生产者单消费者)可以实现无锁、避免互斥锁的开销。

5.性能优势:预分配内存,无动态分配 、操作时间复杂度:O(1) 、 缓存友好:连续内存访问

使用场景:

生产者-消费者模式、网络数据包处理、音频/视频流处理、消息队列(低延迟场景)

#include <atomic> #include <cstddef> #include <type_traits> template<typename T, std::size_t Capacity> class RingBuffer { public: static_assert(Capacity && !(Capacity & (Capacity - 1)), "Capacity must be power of 2"); //位运算检查是否为2的幂 RingBuffer() : read_(0), write_(0) {} ~RingBuffer() { std::size_t r = read_.load(std::memory_order_relaxed); std::size_t w = write_.load(std::memory_order_relaxed); while (r != w) { reinterpret_cast<T *>(&buffer_[r])->~T(); r = (r + 1) & (Capacity - 1); } } // 这里使用万能引用和完美转发,支持左值和右值 template<typename U> bool Push(U && value) { const std::size_t w = write_.load(std::memory_order_relaxed); const std::size_t next_w = (w + 1) & (Capacity - 1); // 检查缓冲区是否满 if (next_w == read_.load(std::memory_order_acquire)) { return false; } new (&buffer_[w]) T(std::forward<U>(value)); write_.store(next_w, std::memory_order_release); return true; } bool Pop(T & value) { const std::size_t r = read_.load(std::memory_order_relaxed); // 检查缓冲区是否空 if (r == write_.load(std::memory_order_acquire)) { return false; } // 取出元素并析构 value = std::move(*reinterpret_cast<T *>(&buffer_[r])); reinterpret_cast<T *>(&buffer_[r])->~T(); read_.store((r + 1) & (Capacity - 1), std::memory_order_release); return true; } std::size_t Size() const { const std::size_t r = read_.load(std::memory_order_acquire); const std::size_t w = write_.load(std::memory_order_acquire); return (w >= r) ? (w - r) : (Capacity - r + w); } private: //cache line 64B alignas(64) std::atomic<std::size_t> read_; alignas(64) std::atomic<std::size_t> write_; alignas(64) std::aligned_storage_t<sizeof(T), alignof(T)> buffer_[Capacity]; };
alignas(64) std::atomic<std::size_t> read_; alignas(64) std::atomic<std::size_t> write_; alignas(64) std::aligned_storage_t<sizeof(T), alignof(T)> buffer_[Capacity];
  • 两个原子变量read_write_分别由消费者和生产者修改

  • 缓存行对齐:避免伪共享

  • 原始内存存储:支持非POD类型

static_assert(Capacity && !(Capacity & (Capacity - 1)), "Capacity must be power of 2");

这行static_assert用于编译时检查Capacity是否是 2的幂(如 1, 2, 4, 8, 16, 32, 64...)。

static_assert是 C++11 引入的编译时断言,用于在编译期间检查条件是否满足。如果条件为false,编译器会报错并显示自定义错误信息。

内存序的设置:

生产者读取生产者索引 write_ 时用relaxed,生产者读取消费者索引 read_ 时用acquire,生产者修改生产者索引时用release。

if (next_w == read_.load(std::memory_order_acquire)) { return false; } new (&buffer_[w]) T(std::forward<U>(value)); write_.store(next_w, std::memory_order_release); return true;

acquire避免下方代码优化到上方,确保先检查队列是否满再决定下一步。

release避免上方代码优化到下方,确保元素成功插入后再更新write_索引。

消费者读取消费者索引 read_ 时用relaxed,消费者读取生产者索引 write_ 时用acquire,消费者者修改消费者者索引时用release。

if (r == write_.load(std::memory_order_acquire)) { return false; } // 取出元素并析构 value = std::move(*reinterpret_cast<T *>(&buffer_[r])); reinterpret_cast<T *>(&buffer_[r])->~T(); read_.store((r + 1) & (Capacity - 1), std::memory_order_release); return true;

acquire避免下方代码优化到上方,确保先检查队列是否为空再决定下一步。

release避免上方代码优化到下方,确保元素成功取出后再更新read_索引。

为什么要alignas对齐?

alignas(64) std::atomic<std::size_t> read_; alignas(64) std::atomic<std::size_t> write_; alignas(64) std::aligned_storage_t<sizeof(T), alignof(T)> buffer_[Capacity];

缓存行(64字节)是数据传输的最小单位,通过alignas(64)把变量存入不同的缓存行避免伪共享。

如果没有对齐可能会出现:

1. 生产者修改 read_ → 整个缓存行变为"脏" 2. 消费者要修改 write_ → 缓存行失效,重新加载 3. 即使修改的是不同变量,也会互相影响 4. 造成缓存行在核心之间"乒乓"效应,性能急剧下降

如何判断空和满

由于数组是环形的,空状态和满状态看起来一样(都是两个指针指向同一位置),所以必须保留一个空位来区分。

当读指针等于写指针时,表示缓冲区为空,没有数据可读。当写指针的下一个位置等于读指针时,表示缓冲区已满,不能再写入数据。这个"下一个位置"的计算就是索引加一后对容量取模。

因此,容量为 N 的环形缓冲区实际最多只能存储 N 减一个元素。

写入数据的过程

写入数据时,首先读取当前的写指针位置,然后计算下一个写位置,即当前写位置加一后对容量取模。接着检查下一个写位置是否等于当前的读指针,如果相等说明缓冲区已满,写入失败。如果不相等,就在当前写指针指向的位置构造数据对象,最后将写指针更新为刚才计算出的下一个写位置。

读取数据的过程

读取数据时,首先读取当前的读指针位置,然后检查读指针是否等于当前的写指针,如果相等说明缓冲区为空,读取失败。如果不相等,就从读指针指向的位置取出数据,然后析构该位置的对象,最后将读指针更新为下一个位置,即当前读位置加一后对容量取模。

指针如何循环

当指针到达数组最后一个索引时,加一后对容量取模的结果为零,指针就自动回到了数组开头,实现了环形效果。例如容量为八时,索引七的下一个位置是零,索引三的下一个位置是四。

http://www.jsqmd.com/news/1317168/

相关文章:

  • WorkshopDL终极指南:三步免费获取Steam创意工坊模组,打破平台壁垒的完整解决方案
  • 终极视频下载神器:3分钟学会用res-downloader轻松下载全网资源
  • Kubernetes中文教程:从零到精通的完整实战指南
  • UE5 CommonUI框架实战:构建现代化游戏菜单系统
  • 如何高效实现数据解析:专业开源工具的实战指南与架构突破
  • Linux内核-文件系统-文件系统目录和文件操作
  • 2026 年新发布:温州诚信的仓储货物输送设备生产商哪家靠谱,别再盲目囤货了!这玩意儿能让你的仓储效率翻3倍还不用加人 - 鉴选官
  • OpenAI Codex 从零接入实战:API调用、提示词工程与代码生成工具开发
  • Unity RPG游戏开发实战:从模块化架构到数据驱动设计
  • 今天不学AI劳动技能,明年简历将被HR系统自动归入“低适配”队列
  • 大模型时代必备术语清单(含中英对照+使用场景+常见误用),限免领取最后48小时
  • 2026年精选:四川债权纠纷法律服务,资深律师如何破局 - 装修教育财税推荐2026
  • 如何快速上手DouK-Downloader:抖音TikTok数据采集与批量下载终极指南
  • SpringBoot高校超市管理系统开发实战
  • 如何用AI制定可执行的个人训练计划:从目标拆解到动态调整
  • 安卓启动图.9.png制作全攻略:九宫格原理、工具实操与避坑指南
  • OpenCV C++基于CNN模型的场景文本检测(OCR)
  • 【AI竞品动态追踪黄金标准】:ISO/IEC 23053合规框架下,构建企业级竞品情报中枢的6步实施路径
  • 单图3D重建实战:基于NeRF与参数化模型创建可驱动数字人
  • 2026下半年济南槐荫汽车补胎指南:富通汽修火补轮胎一站式服务深度解析 - 装修教育财税推荐2026
  • 工业相机型号101M-8001280-IPS-CT-K深度解析:选型、配置与实战避坑指南
  • UEFI变量解析:从固件到操作系统的持久化数据交换机制
  • 9款科研效率工具推荐:从文献管理到论文写作
  • 个人信息泄漏检测技术架构:如何实现隐私安全的API查询系统
  • Mamba 状态空间模型架构深度解析:从 S4 到 Mamba-3 的后 Transformer 序列建模新范式
  • 解决UE5.2.1中Quixel Bridge的uAsset不可用错误:从诊断到修复
  • 从原理到实战:系统性排查与解决502网关错误的完整指南
  • FPGA开发入门:从零构建3-8译码器,掌握Quartus全流程
  • CATIA V5R21在Win10系统下的完整安装与破解指南
  • 2026 年新消息:北塔靠谱的3000型二手沥青搅拌站生产商联系电话,花百万买新设备,为啥有人偏爱这台二手大家伙? - 品质体验官