无锁队列—C++内存序最佳实践

article/2025/6/8 0:03:12

叙述方式:

1.背景介绍 (使用场景)

2.讲结论 (无锁队列实现)

3.讲内存序的使用(通用方式)

一、背景

本文通过一个“单生产者-单消费者”模型的场景,讲解基于C++原子操作和内存序实现的无锁队列

在生产者-消费者模型中,两者通过一个共享的任务(消息)队列实现数据传递,同时这个队列需要保证线程安全

1.多生产者-多消费者(MPMC):

  • 多个生产者之间需要保证线程安全(不能同时访问任务队列的同一个位置插入任务)
  • 多个消费者之间需要保证线程安全(不能同时访问任务队列的同一个位置取出任务)
  • 生产者和消费者之间需要线程安全(不能同时操作同一个队列)

2.多生产者-单消费者(SPMC)

  • 多个生产者之间需要保证线程安全(不能同时访问任务队列的同一个位置插入任务)
  • 生产者和消费者之间需要线程安全(不能同时操作同一个队列)

3.单生产者-多消费者(MPSC)

  • 多个消费者之间需要保证线程安全(不能同时访问任务队列的同一个位置取出任务)
  • 生产者和消费者之间需要线程安全(不能同时操作同一个队列)

4.单生产者-单消费者(SPSC)

  • 生产者和消费者之间需要线程安全(不能同时操作同一个队列)

以上是所有生产-消费者模型的线程安全问题,本文讲解SPSC无锁队列的实现

首先,为什么不用mutex保护临界区?我们需要更好的性能:

在这里插入图片描述

二、结论

先贴源码:

template<typename T, size_t Capacity>
class SPSCQueue {
public:SPSCQueue() : head_(0), tail_(0) {}bool enqueue(const T& item) {size_t next_head = (head_ + 1) % Capacity;if (next_head == tail_) return false;  // fullbuffer_[head_] = item;std::atomic_thread_fence(std::memory_order_release);  // 保证数据写入在更新 head_ 前完成head_ = next_head;return true;}bool dequeue(T& item) {if (tail_ == head_) return false;  // emptystd::atomic_thread_fence(std::memory_order_acquire);  // 保证读取数据在读取 head_ 后进行item = buffer_[tail_];tail_ = (tail_ + 1) % Capacity;return true;}private:T buffer_[Capacity];size_t head_; // 仅生产者访问size_t tail_; // 仅消费者访问
};

这样一个队列,可以实现无锁化的线程安全,他是怎么做到的?

1.首先head和tail两个队列指针,得益于单生产者-单消费者,天然没有并发安全问题

size_t head_; // 仅由生产者线程访问
size_t tail_; // 仅由消费者线程访问

但是buffer共享,于是分别分析生产者-消费者:

2.enqueue 分析(生产者线程)

buffer_[head_] = item;
std::atomic_thread_fence(std::memory_order_release); // 此行后的更新写操作不能被向前重排,也就保证2、3行的顺序执行
head_ = next_head;

效果:保证数据写入buffer之后,再更新head指针,保证对 buffer_ 的写入先于 head_ 的更新对消费者可见

3.dequeue 分析(消费者线程)

if (tail_ == head_) return false;  // empty
std::atomic_thread_fence(std::memory_order_acquire); // 此行后的读操作不能向前重排,即保证先1后3行,先判断后取值
item = buffer_[tail_];
tail_ = (tail_ + 1) % Capacity;

buffer_ 读取数据之前,先执行 acquire 屏障;这样可确保读取到的 buffer_ 中的数据,是生产者在对应 head_ 更新前写入的,也就是避免了消费者误判队列为空

三、内存序的使用

只讲解常用的两个:release和acquire

1.release

// Thread A
data = 42;
flag.store(1, std::memory_order_release);

含义是:

  • 编译器和 CPU 不得把 data = 42 移动到 flag.store 之后
  • 也就是说,在 store-release 之前的写操作,不能重排到 store-release 之后
  • 但 store-release 之后的读写操作(在当前线程中)可以重排到它之前

store-release 保证的语义:

  • 对写线程本身来说,release 之前的写,先于 release 发生
  • 对其他线程来说,如果它通过 acquire-load 看到这个 release-store 的值,它也就能看到这个 store-release 之前的写

2.acquire

// Thread B
if (flag.load(std::memory_order_acquire) == 1) {int x = data;
}

含义是:

  • 编译器和 CPU 不得把 data 的读取(即 load-acquire 之后的操作)重排到 load-acquire 之前
  • load-acquire 之前的操作是可以被重排到之后的

load-acquire 保证的语义:

  • acquire 之后的读写不能被重排到前面
  • 如果 acquire-load 成功观察到了 release-store 的值,那么它也能看到写线程在 release-store 之前的写操作。

最后附上刚刚无锁队列代码更加现代化的写法,刚刚实例代码只为叙述原理比较清晰:

template<typename T, size_t Capacity>
class SPSCQueue {
public:SPSCQueue() : head_(0), tail_(0) {}bool enqueue(const T& item) {size_t head = head_.load(std::memory_order_relaxed);size_t next_head = (head + 1) % Capacity;if (next_head == tail_.load(std::memory_order_acquire)) {return false; // full}buffer_[head] = item;head_.store(next_head, std::memory_order_release);return true;}bool dequeue(T& item) {size_t tail = tail_.load(std::memory_order_relaxed);if (tail == head_.load(std::memory_order_acquire)) {return false; // empty}item = buffer_[tail];tail_.store((tail + 1) % Capacity, std::memory_order_release);return true;}private:T buffer_[Capacity];std::atomic<size_t> head_; // 仅生产者写,消费者读std::atomic<size_t> tail_; // 仅消费者写,生产者读
};

http://www.hkcw.cn/article/ayrEGDHMsb.shtml

相关文章

ADC模数转换控制

目录 1. Convst信号的功能本质 1.1 核心作用 1.2 关键优势 1.3 Convst与SPI接口的协作关系 2.实际设计要点 2.1 硬件连接方案 2.2 时序约束&#xff08;以AD7685为例&#xff09; 2.3 多片ADC同步策略 3.高级应用技巧 3.1 动态调整采样率 3.2 抗干扰设计 3.3 故障排查 4.总…

QT常用控件(1)

控件是构成QT的基础元素&#xff0c;例如Qwidget也是一个控件&#xff0c;提供了一个‘空’的矩形&#xff0c;我们可以往里面添加内容和处理用户输入&#xff0c;例如&#xff1a;按钮&#xff08;QpushButton&#xff09;&#xff0c;基础显示控件&#xff08;Lable&#xff…

Linux系统-基本指令(5)

文章目录 mv 指令cat 指令&#xff08;查看小文件&#xff09;知识点&#xff08;简单阐述日志&#xff09;more 和 less 指令&#xff08;查看大文件&#xff09;head 和 tail 指令&#xff08;跟查看文件有关&#xff09;知识点&#xff08;管道&#xff09;时间相关的指令&a…

C 语言学习笔记(预处理和库文件)

内容提要 预处理库文件 预处理 预处理编译汇编链接 什么是预处理 预处理就是在源文件&#xff08;.c文件&#xff09;编译之前&#xff0c;所进行的一部分预备操作&#xff0c;这部分操作是由预处理器&#xff08;预处理程序&#xff09;自动完成。当源文件在编译时&#x…

谷歌地图高清卫星地图软件(Google Earth)v6.0.3.2197 中文版 - 前端工具导航

谷歌地图6.0Google Earth是一款谷歌地图高清卫星地图软件&#xff0c;能够实时监测并提供最准确的地图信息&#xff0c;地球上的任意一块地区都能够准确定位并放大查看&#xff0c;覆盖范围广&#xff0c;精度高&#xff0c;非常实用&#xff01; 谷歌卫星高清地图 下载链接&a…

全球治理指标数据(1996-2023)

1945 全球治理指标&#xff08;WGI&#xff09;(1996-2023&#xff09; 数据简介 全球治理指标&#xff08;WGI&#xff09;是一个由世界银行开发的综合性数据库&#xff0c;通过政治稳定、政府效能、监管质量、法治水平、腐败控制和公民话语权六个维度系统衡量全球各国的治理…

Blocked aria-hidden on an element because its descendant retained focus.

问题出在 Element UI 的 el-table 组件 全选功能上&#xff0c;这是一个常见的无障碍&#xff08;a11y&#xff09;问题。这个错误提示与网页 accessibility&#xff08;无障碍访问&#xff09;相关&#xff0c;涉及 aria-hidden 属性的不当使用。 问题原因分析 1. Element U…

2025 年人脸识别技术应用备案政策已落地

在 AI 技术深度渗透的当下&#xff0c;人脸识别作为重要的生物识别技术&#xff0c;已广泛应用于安防、金融、零售等多领域。但随之而来的个人信息安全风险也备受关注。2025 年 6 月 1 日起《人脸识别技术应用安全管理办法》正式实施&#xff0c;企业需重视人脸识别技术应用备案…

01电气设计-380V强电部分设计

目标&#xff1a;在电气设计过程中380V的强电部分&#xff0c;一般来自与工厂&#xff0c;一般为3相5线制的380V&#xff0c;下面的应用场景是当我的用电设备&#xff08;电机&#xff0c;冷水机&#xff0c;控制器&#xff0c;驱动器&#xff0c;激光器等等&#xff09;总功率…

文件批量重命名

mv只支持单个文件命名 批量重命名用rename 例子&#xff1a; #touch命令批量创建空文件&#xff0c;文件10-15 touch file{10..15}.txt批量重命名 # 批量重命名&#xff0c;file10-15重命名为test10-15 #这里file1? 匹配的是单个字符。比如10,11等 rename file1 test1 file1…

ES的开始

ES作用 在海量数据中&#xff0c;执行搜索功能&#xff0c;使用mysql&#xff0c;效率过低&#xff0c; 如果关键字输入不准确&#xff0c;一样可以搜索到想要的数据 讲搜索关键字&#xff0c;以红色字体展示 ES介绍 ES是基于java语言并且基于Lucene编写的搜索引擎框架&#x…

【论文解读】ReAct:从思考脱离行动, 到行动反馈思考

认识从实践开始&#xff0c;经过实践得到了理论的认识&#xff0c;还须再回到实践去。 ——《实践论》,毛泽东 1st author: About – Shunyu Yao – 姚顺雨 paper [2210.03629] ReAct: Synergizing Reasoning and Acting in Language ModelsReAct: Synergizing Reasoning and…

AXURE-动态面板

1.概述 动态面板原件&#xff0c;容器类的原件一个动态面板可以有多种状态 同一时刻只展示一个状态 默认展示第一个状态 主要用于多个状态的切换可拖动 1.1 创建 将原件库中的“动态面板”原件&#xff0c;直接拖动到工作区中&#xff0c;创建空白动态面板将页面中原件选中…

AI地面垃圾检测算法智能分析网关V4打造城市/公园/校园等场景环保卫生监管解决方案

一、方案背景​ 在城市管理与场所运营中&#xff0c;地面垃圾的及时清理是环境品质的重要指标。传统人工巡检效率低、成本高&#xff0c;存在明显滞后性&#xff0c;难以满足现代环境管理需求。随着人工智能与计算机视觉技术发展&#xff0c;智能化管理成为趋势。AI智能分析网…

帝国CMS QQ登录插件最新版 获取QQ头像和QQ昵称

帝国CMS QQ登录插件最新版 获取QQ头像和QQ昵称 QQ一键登录&#xff0c;免邮箱 随机密码 获取QQ头像 获取QQ昵称 直接下载上传到帝国CMS&#xff1a;/e/memberconnect UTF-8版本 GBK的自己转换 QQ登录后的默认密码 是随机的邮箱账号前面的随机6个字母和数字 【下图字母数…

Kafka 的优势是什么?

Kafka 作为分布式流处理平台的核心组件&#xff0c;其设计哲学围绕高吞吐、低延迟、高可扩展性展开&#xff0c;在实时数据管道和大数据生态中具有不可替代的地位。 一、超高吞吐量与低延迟 1. 磁盘顺序 I/O 优化 突破磁盘瓶颈&#xff1a;Kafka 将消息持久化到磁盘&#xff…

低谷才是出成绩

有些朋友说我现在是高光&#xff0c;其实不然 之所以有这样的误解&#xff0c;是我个人的简历上是不断增加名誉。这点属实&#xff0c;看看我的词条&#xff1a;https://www.modb.pro/wiki/4245的确如此。但是其实也有误会。事情可以反过来看。因为&#xff0c;如果做技术的在…

Bash shell四则运算

文章目录 四则运算1. ‌expr 命令‌2. ‌$(( )) 表达式&#xff08;推荐&#xff09;‌3. ‌$[ ] 表达式&#xff08;已弃用&#xff09;‌4. ‌let 命令‌小数运算i 和 i 区别 四则运算 算术运算&#xff1a; - * / %&#xff08;取模&#xff0c;求余数&#xff09; Bash sh…

Windows + CPU也能跑时序预测:TSLib框架快速上手与踩坑避雷

在时序预测领域,选择一个成熟的框架往往能让我们事半功倍。最近接手了一个紧急的时序预测项目,经过一番调研后,我选择了TSLib(Time-Series-Library)这个优秀的开源框架来快速搭建整个预测流程。 由于开发环境限制在Windows平台且没有GPU支持,整个部署过程还是遇到了一些…

多模态大语言模型arxiv论文略读(105)

UnifiedMLLM: Enabling Unified Representation for Multi-modal Multi-tasks With Large Language Model ➡️ 论文标题&#xff1a;UnifiedMLLM: Enabling Unified Representation for Multi-modal Multi-tasks With Large Language Model ➡️ 论文作者&#xff1a;Zhaowei…