0

0

c++如何实现一个无锁环形缓冲区_c++ Lock-Free Ring Buffer【并发】

冰火之心

冰火之心

发布时间:2025-12-08 21:25:02

|

196人浏览过

|

来源于php中文网

原创

无锁环形缓冲区核心是用原子操作(CAS)管理读写指针,容量需为2的幂以支持位运算取模;SPSC场景可仅用acquire/release内存序,MPMC则需版本号或双字CAS防ABA问题。

c++如何实现一个无锁环形缓冲区_c++ lock-free ring buffer【并发】

实现一个无锁环形缓冲区(Lock-Free Ring Buffer)在 C++ 中核心在于:用原子操作管理读写指针,避免数据竞争,同时处理好 ABA 问题和内存序。它不是“完全无同步”,而是用 std::atomic 替代互斥锁,靠 CAS(Compare-And-Swap)保证线性一致性。

环形缓冲区结构设计要点

容量必须是 2 的幂(如 1024、4096),这样可用位运算快速取模:index & (capacity - 1) 替代 % capacity,避免分支和除法开销。缓冲区本身用 std::array 或堆分配的 T* 存储元素;注意 T 必须是 trivially copyable(或手动管理构造/析构)。

  • 两个原子指针:std::atomic read_idx_write_idx_,初始为 0
  • 不直接存“已用长度”,而用指针差值判断空/满:当 (write_idx_ - read_idx_) == capacity 时满;相等时空
  • 为避免 ABA 问题(尤其在多生产者/多消费者场景),可对指针高位打包 epoch 或使用 double-word CAS(如 std::atomic 拆高低 32 位存索引+版本)

单生产者单消费者(SPSC)最简实现

SPSC 是唯一能用纯单原子变量 + 内存序搞定的场景,无需版本号或双字 CAS。关键在于:生产者只改 write_idx_,消费者只改 read_idx_,彼此不干扰。

  • 写入时:先读 read_idx_.load(std::memory_order_acquire) 得当前读位置,算出可写空间;若足够,把数据拷贝进缓冲区对应槽位,再用 store(std::memory_order_release) 更新 write_idx_
  • 读取时:同理,先读 write_idx_.load(std::memory_order_acquire),再拷贝,最后更新 read_idx_,用 release 确保数据对后续读可见
  • 注意:T 若含非平凡构造/析构(如 std::string),需用 placement new + 显式调用 destructor,或仅支持 trivial 类型

多生产者或多消费者(MPMC)需升级策略

MPMC 下多个线程可能同时修改同一指针,必须用 CAS 循环重试。此时单纯 size_t 原子不够——ABA 会导致误判(例如:A→B→A,CAS 认为没变但中间已有数据被消费)。常见解法:

蕉点AI
蕉点AI

AI电商商品图生成平台 | 智能商品素材制作工具

下载

立即学习C++免费学习笔记(深入)”;

  • 使用 std::atomic,高 32 位存版本号(每次 CAS 成功递增),低 32 位存索引;CAS 时同时比对整个 64 位
  • 或采用经典的 “Dmitry Vyukov ring buffer” 设计:每个槽位加一个原子状态字段(如 std::atomic state_,-1=空,0=写中,1=就绪),用三态状态机协调
  • 内存序选 std::memory_order_acq_rel 配合 CAS,确保读写依赖正确同步

实际使用中的关键细节

无锁 ≠ 无脑快。很多性能陷阱藏在细节里:

  • 缓存行对齐:alignas(64) 对读写指针和缓冲区首尾做对齐,避免 false sharing(多个原子变量落在同一缓存行导致频繁失效)
  • 编译器重排防护:即使用了原子操作,也要显式指定 memory order,不能依赖默认(seq_cst 过重,acquire/release 通常足够)
  • 边界检查与返回值:push/pop 应返回 bool 表示成功与否,而非抛异常或阻塞;上层需主动轮询或结合 eventfd/condition_variable 做等待
  • 调试难度高:建议先用带锁版本验证逻辑,再逐步替换为原子操作,并用 ThreadSanitizer / TSAN 验证数据竞争

基本上就这些。SPSC 场景下几十行就能写出高效无锁环形队列;MPMC 则推荐直接用成熟库如 rigtorp/MPMCQueueFolly::ProducerConsumerQueue —— 它们已解决版本号、内存回收、模板适配等一揽子问题。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

358

2023.08.02

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

53

2025.08.29

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

100

2025.10.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

394

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

574

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

482

2023.08.10

word背景色怎么改成白色
word背景色怎么改成白色

Word是微软公司的一个文字处理器软件。word为用户提供了专业而优雅的文档工具,帮助用户节省时间并得到优雅美观的结果。word提供了许多易于使用的文档创建工具,同时也提供了丰富的功能供创建复杂的文档使用。怎么word背景色怎么该呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

3709

2023.07.21

word最后一页空白页怎么删除
word最后一页空白页怎么删除

word最后一页空白页删除方法有:通过删除回车符、调整页边距、删除分节符或调整分页符位置,您可以轻松去除最后一页的空白页。根据您实际的文档情况,选择适合您的方法进行操作,使您的文档更加美观和整洁。本专题为大家提供word最后一页空白页怎么删除不了相关的各种文章、以及下载和课程。

322

2023.07.24

c++空格相关教程合集
c++空格相关教程合集

本专题整合了c++空格相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 7.4万人学习

C 教程
C 教程

共75课时 | 4.2万人学习

C++教程
C++教程

共115课时 | 13.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号