0

0

c++如何实现生产者消费者无锁模型_c++ 循环缓冲区与原子下标操作【详解】

尼克

尼克

发布时间:2025-12-29 20:04:02

|

533人浏览过

|

来源于php中文网

原创

不能直接用 std::queue 做无锁生产者消费者,因其 push/pop/size 非原子且 std::atomic 不支持非平凡类型;需用 2 的幂容量循环缓冲区,配原子下标与位掩码实现线性安全。

c++如何实现生产者消费者无锁模型_c++ 循环缓冲区与原子下标操作【详解】

为什么不能直接用 std::queue无锁生产者消费者

因为 std::queue 的所有操作(pushpopsize)都不是原子的,内部有内存分配、指针更新、状态判断等多步操作。即使你把 std::queue 包在 std::atomic 里,也毫无意义——std::atomic 不支持非平凡类型(non-trivial type),编译直接报错:error: use of deleted function 'std::atomic<:queue>>::atomic()'

真正的无锁(lock-free)要求:所有线程对共享数据的读写,仅通过原子操作(如 loadstorecompare_exchange_weak)完成,且不依赖互斥量或条件变量。

  • 必须用固定大小的循环缓冲区(circular buffer),避免运行时内存分配
  • 生产者和消费者各自维护独立的原子下标:std::atomic m_produce_idxstd::atomic m_consume_idx
  • 下标运算必须用模运算或位掩码(推荐后者,更快且编译器可优化为 AND)
  • 缓冲区容量必须是 2 的幂(如 1024、4096),才能安全使用位掩码

如何用原子下标 + 位掩码实现线性安全的环形队列

核心思想:把下标看作无限递增的序列号,但只取低 N 位作为实际数组索引;用高位隐含“版本”或“绕圈次数”,从而区分“满”和“空”状态(避免单独用标志位或牺牲一个槽位)。

假设缓冲区长度 CAPACITY = 1024,则掩码 MASK = CAPACITY - 1(即 0x3FF)。所有索引访问统一用 idx & MASK

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

关键约束:

  • 生产者只能在 (m_consume_idx.load(std::memory_order_acquire) + CAPACITY) > m_produce_idx.load(std::memory_order_acquire) 时写入(即“未满”)
  • 消费者只能在 m_consume_idx.load(std::memory_order_acquire) 时读取(即“非空”)
  • 所有原子读写需配对合适的 memory order:生产者写元素后用 store(..., std::memory_order_release) 更新下标;消费者读下标前用 load(std::memory_order_acquire)
class LockFreeRingBuffer {
    static constexpr size_t CAPACITY = 1024;
    static constexpr size_t MASK = CAPACITY - 1;
    std::array m_buffer;
    std::atomic m_produce_idx{0};
    std::atomic m_consume_idx{0};

public: bool try_push(int val) { auto current_prod = m_produce_idx.load(std::memory_order_acquire); auto current_cons = m_consume_idx.load(std::memory_order_acquire); if (current_prod - current_cons >= CAPACITY) return false; // 已满

    m_buffer[current_prod & MASK] = val;
    m_produce_idx.store(current_prod + 1, std::memory_order_release);
    return true;
}

bool try_pop(int& out) {
    auto current_cons = m_consume_idx.load(std::memory_order_acquire);
    auto current_prod = m_produce_idx.load(std::memory_order_acquire);
    if (current_cons >= current_prod) return false; // 为空

    out = m_buffer[current_cons & MASK];
    m_consume_idx.store(current_cons + 1, std::memory_order_release);
    return true;
}

};

Figma
Figma

Figma 是一款基于云端的 UI 设计工具,可以在线进行产品原型、设计、评审、交付等工作。

下载

std::memory_order 选错会导致什么现象

最常见错误是全用 std::memory_order_relaxed:编译器和 CPU 可能重排指令,导致消费者读到“新下标、旧数据”或“旧下标、新数据”,结果就是读到未初始化值、重复读、甚至崩溃。

正确配对逻辑:

  • 生产者:写数据 → store(新下标, release):确保数据写入对其他线程可见
  • 消费者:load(下标, acquire) → 读数据:确保后续读数据一定看到之前被 release 写入的内容
  • 不能用 acquire 去读生产者下标后,再用 relaxed 去读消费者下标——这会破坏同步关系
  • compare_exchange_weak 场景(如 CAS 更新下标)必须明确指定 success/fail memory order,通常 success 用 acq_rel,fail 用 relaxed

如果你在调试中发现偶发读到 0 或随机大数,大概率是 memory order 失配,而不是逻辑错误。

性能陷阱:缓存行伪共享(false sharing)怎么破

m_produce_idxm_consume_idx 如果在内存中挨得太近(比如同属一个 64 字节缓存行),多核并发读写会触发缓存一致性协议频繁同步,性能断崖式下降——实测吞吐可能比加锁还差。

解决方法只有两个字:隔离。

  • alignas(64) 强制每个原子变量独占缓存行
  • 或者在它们之间插入填充字段(char pad[64]),但不如 alignas 清晰可靠
  • 别忘了 m_buffer 本身也要对齐(尤其当它很大时),否则首尾元素也可能跨缓存行争抢

一个没对齐的无锁队列,跑得越快,伪共享越严重——这不是理论风险,是真实压测中反复验证过的瓶颈点。

相关专题

更多
scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

184

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

260

2023.10.25

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

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

466

2023.08.10

function是什么
function是什么

function是函数的意思,是一段具有特定功能的可重复使用的代码块,是程序的基本组成单元之一,可以接受输入参数,执行特定的操作,并返回结果。本专题为大家提供function是什么的相关的文章、下载、课程内容,供大家免费下载体验。

468

2023.08.04

js函数function用法
js函数function用法

js函数function用法有:1、声明函数;2、调用函数;3、函数参数;4、函数返回值;5、匿名函数;6、函数作为参数;7、函数作用域;8、递归函数。本专题提供js函数function用法的相关文章内容,大家可以免费阅读。

158

2023.10.07

Golang 命令行工具(CLI)开发实战
Golang 命令行工具(CLI)开发实战

本专题系统讲解 Golang 在命令行工具(CLI)开发中的实战应用,内容涵盖参数解析、子命令设计、配置文件读取、日志输出、错误处理、跨平台编译以及常用CLI库(如 Cobra、Viper)的使用方法。通过完整案例,帮助学习者掌握 使用 Go 构建专业级命令行工具与开发辅助程序的能力。

1

2025.12.29

ip地址修改教程大全
ip地址修改教程大全

本专题整合了ip地址修改教程大全,阅读下面的文章自行寻找合适的解决教程。

162

2025.12.26

压缩文件加密教程汇总
压缩文件加密教程汇总

本专题整合了压缩文件加密教程,阅读专题下面的文章了解更多详细教程。

52

2025.12.26

wifi无ip分配
wifi无ip分配

本专题整合了wifi无ip分配相关教程,阅读专题下面的文章了解更多详细教程。

108

2025.12.26

热门下载

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

精品课程

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

共18课时 | 4.1万人学习

Sass 教程
Sass 教程

共14课时 | 0.7万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

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

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