C++ 内存序在多线程中的使用

目录

一、内存顺序

二、 指令重排在多线程中的问题  

2.1 问题与原因

2.2 解决方案 

 三、六种内存序

3.1 memory_order_relaxed

3.2  memory_order_consume

3.3 memory_order_acquire

 3.4 memory_order_release

3.5 memory_order_acq_rel

3.6 memory_order_seq_cst


一、内存顺序

        内存顺序是指在并发编程中, 对内存读写操作的执行顺序. 这个顺序可以被编译器和处理器进行优化, 可能会与代码中的顺序不同, 这被称为指令重排。

        如下代码,如果不处理器不能重拍两个加法的指令,则只能一行一行去执行;但是如果可以重拍指令,则可以在不同的处理单元中并行执行这两个加法操作,发挥处理器执行流水线的优势。

      

int x = 0, y = 1, a = 0, b = 1;void testMemoryOrder() {    a = b + 2;    x = y + 2;
}

二、 指令重排在多线程中的问题  

2.1 问题与原因

        在多线程中指令重排会引起一些问题,比如如下场景:

std::atomic<bool> ready{false};
std::atomic<int> data{0};void producer() {    data.store(100, std::memory_order_relaxed); // 原子性的更新data的值, 但是不保证内存顺序 ready.store(true, std::memory_order_relaxed); // 原子性的更新ready的值, 但是不保证内存顺序
}void consumer() {    // 原子性的读取ready的值, 但是不保证内存顺序    while (!ready.load(memory_order_relaxed)) {          std::this_thread::yield(); //让出CPU时间片   }   // 当ready为true时, 再原子性的读取data的值    std::cout << data.load(memory_order_relaxed);  // 4. 消费者线程使用数据
}int main() {       std::thread t1(producer);       std::thread t2(consumer);    t1.join();    t2.join();   return 0;
}

         我们预期的效果应该是当消费者看到ready为true时, 此时再去读取data的值。但实际的情况是, 消费者看到ready为true后, 读取到的data值可能仍然是0。

        一方面可能是指令重排引起的:在producer线程里, data和store是两个不相干的变量, 所以编译器或者处理器可能会将data.store(100, std::memory_order_relaxed);重排到ready.store(true, std::memory_order_relaxed);之后执行, 这样consumer线程就会先读取到ready为true, 但是data仍然是0。

       另一方面可能是内存顺序不一致引起的: 即使producer线程中的指令没有被重排, 但CPU的多级缓存会导致consumer线程看到的data值仍然是0。下面这张示意图来说明这个问题和CPU多级缓存的关系。

        每个CPU核心都有自己的L1 Cache与L2 Cache。producer线程修改了data和ready的值, 但修改的是L1 Cache中的值,producer线程和consumer线程的L1 Cache并不是共享的,所以consumer线程不一定能及时的看到producer线程修改的值。CPU Cache的同步是件很复杂的事情, 生产者更新了data和ready后,还需要根据MESI协议将值写回内存,并且同步更新其他CPU核心Cache里data和ready的值,这样才能确保每个CPU核心看到的data和ready的值是一致的。而data和ready同步到其他CPU Cache的顺序也是不固定的,可能先同步ready,再同步data, 这样的话consumer线程就会先看到ready为true, 但data还没来得及同步,所以看到的仍然是0。 

2.2 解决方案 

void producer() 
{   data.store(100, std::memory_order_relaxed); // 原子性的更新data的值, 但是不保证内存顺序   ready.store(true, std::memory_order_released); // 保证data的更新操作先于ready的更新操作
}void consumer() 
{    // 保证先读取ready的值, 再读取data的值    while (!ready.load(memory_order_acquire)) {           std::this_thread::yield(); }    // 当ready为true时, 再原子性的读取data的值    std::cout << data.load(memory_order_relaxed);}

  1. ready.store(true, std::memory_order_released):一方面限制ready之前的所有操作不得重排到ready之后,以保证先完成data的写操作, 再完成ready的写操作。 另一方面保证先完成data的内存同步, 再完成ready的内存同步,以保证consumer线程看到ready新值的时候,一定也能看到data的新值。

  2. ready.load(memory_order_acquire): 限制ready之后的所有操作不得重排到ready之前, 以保证先完成读ready操作,再完成data的读操作;

 三、六种内存序

        多线程程序中,为了保证程序的一致性和正确性,需要对内存操作的顺序进行控制。因为在现代处理器中,由于缓存一致性、乱序执行等优化,指令可能不会按顺序执行。内存序允许开发者显式地指定不同操作的顺序,以保证数据的一致性。      
        C++11 引入了 <atomic> 头文件,并定义了几种内存序类型,来控制原子操作的执行顺序,std::atomic提供了以下几个常用接口来实现原子性的读写操作:

// 原子性的写入值

std::atomic<T>::store(T val, memory_order sync = memory_order_seq_cst);

// 原子性的读取值

std::atomic<T>::load(memory_order sync = memory_order_seq_cst);

// 原子性的增加 counter.fetch_add(1)等价于++counter

std::atomic<T>::fetch_add(T val, memory_order sync = memory_order_seq_cst);

// 原子性的减少 counter.fetch_sub(1)等价于--counter

std::atomic<T>::fetch_sub(T val, memory_order sync = memory_order_seq_cst);

// 原子性的按位与 counter.fetch_and(1)等价于counter &= 1

std::atomic<T>::fetch_and(T val, memory_order sync = memory_order_seq_cst);

// 原子性的按位或 counter.fetch_or(1)等价于counter |= 1

std::atomic<T>::fetch_or(T val, memory_order sync = memory_order_seq_cst);

// 原子性的按位异或 counter.fetch_xor(1)等价于counter ^= 1

std::atomic<T>::fetch_xor(T val, memory_order sync = memory_order_seq_cst);

        memory_order用于指定内存顺序不同的内存序提供了不同的同步和顺序保证,从最弱到最严格依次如下 :

memory_order_relaxed(松散顺序)
memory_order_consume(消费顺序)
memory_order_acquire(获取顺序)
memory_order_release(释放顺序)
memory_order_acq_rel(获取-释放顺序)
memory_order_seq_cst(顺序一致性)

3.1 memory_order_relaxed

        基本概念:最宽松的内存序,它只保证操作的原子性,不涉及任何线程间的同步或顺序保证。这种方式下,编译器和CPU可以任意重排指令,但仍然保证操作是原子的。

        应用场景: 当只需要在多线程环境中执行简单的原子操作,而不需要与其他线程同步时,memory_order_relaxed 是最佳选择,典型场景是简单的计数器或统计类操作。 

std::atomic<int> counter(0);
//能保证原子性统计最终一致
void increment() {for (int i = 0; i < 100; ++i) {counter.fetch_add(1, std::memory_order_relaxed); // 放松顺序}
}

3.2  memory_order_consume

        基本概念:消费顺序,用于确保在同一线程中,依赖于原子操作结果的读操作不会被重排到该原子操作之前。虽然设计上适用于生产者-消费者模型,但由于硬件优化,memory_order_consume 通常等同于 memory_order_acquire。

        应用场景:在多线程中,当一个线程生产数据,另一个线程消费数据并依赖这些数据时,可以使用 memory_order_consume。

std::atomic<int> p;void producer() {p.store(100, std::memory_order_release); // 发布数据
}void consumer() {int p = ptr.load(std::memory_order_consume); std::cout << "Consumed: " << p << std::endl;}

3.3 memory_order_acquire

        基本概念:获取顺序,确保在当前线程中,会在读操作之后插入一个LoadLoad屏障, 确保屏障之后的所有操作不会重排到屏障之前。这意味着,在线程读取数据后,它可以看到其他线程对共享变量的修改。

        使用场景:当你需要在读取数据时,确保前面的操作已经完成。

std::atomic<int> flag(0);
int data = 0;void writer() {data = 100;flag.store(1, std::memory_order_release); // 先改data更新flag
}void reader() {while (flag.load(std::memory_order_acquire) != 1); // 等待flag更新std::cout << "Data: " << data << std::endl; // 保证读取到最新的data值
}

 3.4 memory_order_release

        基本概念:释放顺序,确保在当前线程中,会在写操作之前插入一个StoreStore屏障, 确保屏障之前的所有操作不会重排到屏障之后。这意味着,其他线程在看到这个原子操作之后,也可以看到该线程之前的所有修改。

        使用场景:当你需要在更新共享数据时,确保在更新操作之前的写入已经完成。同上 flag.store(1, std::memory_order_release);保证执行到此处时,前面的data=100已经执行且对其他线程可见。

3.5 memory_order_acq_rel

        基本概念:获取-释放顺序,等效于memory_order_acquire和memory_order_release的组合,同时插入一个StoreStore屏障与LoadLoad屏障,确保当前线程中的操作既能看到其他线程的修改,又可以发布自己的修改。这种顺序适用于同时需要同步读写的场景。

        应用场景:当一个线程既要读取其他线程的状态,又要写入新的状态时,使用 memory_order_acq_rel。例如锁的实现。它的作用是确保 线程 B 在读取 x 时,所有之前对 x 的修改(或者其他操作)都已经完成,并且不会被延迟到 x 被读取之后,线程 B 之后的操作(比如打印 x 的值)不会在 x 读取之前执行。

std::atomic<int> x{0};void threadA() {x.fetch_add(1, std::memory_order_acq_rel);  // 获取-释放操作
}void threadB() {// 获取操作while (x.load(std::memory_order_acquire) == 0) {  //等待交出时间片}std::cout << "Thread B can proceed";
}

3.6 memory_order_seq_cst

        基本概念:顺序一致性(Sequentially Consistent),保证所有线程看到的操作顺序是一致的,原子变量默认顺序。

        被memory_order_seq_cst标记的写操作,会立马将新值写回内存,而不仅仅只是写到Cache里就结束了;被memory_order_seq_cst标记的读操作,会立马从内存中读取新值,而不是直接从Cache里读取。这样相当于多个线程读写都在一个内存中,也就不存在Cache同步的顺序不一致问题。相比其他的memory_order,memory_order_seq_cst当于禁用了CPU Cache,会带来最大的性能开销了。

        使用场景:当需要确保所有线程对全局状态的顺序一致时,使用 memory_order_seq_cst。它适合那些需要绝对严格的同步场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/28426.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型+知识图谱:重塑企业制度标准管理

在数字化转型的浪潮中&#xff0c;制度标准管理领域正迎来一场革命性的变革。借助大模型和知识图谱等前沿人工智能技术&#xff0c;制度标准管理不再仅仅是简单的文档存储和检索&#xff0c;而是演变为一个智能化、高效化、精准化的管理体系。 1.关键技术 我们的制度标准管理…

FPGA学习(一)——DE2-115开发板编程入级

FPGA学习&#xff08;一&#xff09;——DE2-115开发板编程入级 一、实验目的 通过 1 位全加器的详细设计&#xff0c;深入掌握原理图输入以及 Verilog 的两种设计方法&#xff0c;熟悉 Quartus II 13.0 软件的使用流程&#xff0c;以及在 Intel DE2-115 开发板上的硬件测试过…

【大模型基础_毛玉仁】1.2 基于RNN的语言模型

【大模型基础_毛玉仁】1.2 基于RNN的语言模型 1.2 基于RNN的语言模型1.2.1 循环神经网络RNN1.2.2 基于RNN的语言模型1&#xff09;概率说明&#xff1a;2&#xff09;损失函数3&#xff09;问题 1.2 基于RNN的语言模型 循环神经网络&#xff08;RecurrentNeuralNetwork,RNN&am…

三参数水质在线分析仪:从源头保障饮用水安全

【TH-ZS03】饮用水安全是人类健康的重要保障&#xff0c;其质量直接关系到人们的生命健康。随着工业化、城市化的快速发展&#xff0c;水体污染问题日益严峻&#xff0c;饮用水安全面临着前所未有的挑战。为了从源头保障饮用水安全&#xff0c;科学、高效的水质监测手段必不可少…

MiniMind用极低的成本训练属于自己的大模型

本篇文章主要讲解&#xff0c;如何通过极低的成本训练自己的大模型的方法和教程&#xff0c;通过MiniMind快速实现普通家用电脑的模型训练。 日期&#xff1a;2025年3月5日 作者&#xff1a;任聪聪 一、MiniMind 介绍 基本信息 在2小时&#xff0c;训练出属于自己的28M大模型。…

后验概率估计

前言 本文隶属于专栏《机器学习数学通关指南》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见《机器学习数学通关指南》 正文 一、基本定义 &#x1f9ee; …

错误: 加载主类时出现 LinkageError,java.lang.UnsupportedClassVersionError 解决方案

分析: 可能就是我们在配置完jdk的path时候,电脑没有重启idea还没有更新path环境jdk版本. 解决办法: 1.重启电脑 2.seting设置对应的jdk版本 Project Structure中设置jdk版本 运行就解决了 一键三连 一起学习 一起进步. 推动科技发展, 为科技赋能.

学习记录-用例设计编写

黑马测试视频记录 目录 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法​编辑 5、错误推荐法 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法 5、错误推荐法 时间紧任务重…

软件测试(三)——Bug篇

文章目录 Bug篇软件测试的生命周期BugBug的概念Bug的要素Bug的级别Bug的生命周期 与开发发生争执怎么办 Bug篇 大部分的Bug都是测试人员提出的&#xff0c;因此在Bug篇的开始会先介绍软件测试的生命周期。同时&#xff0c;了解软件测试的生命周期能帮助我们了解测试的工作&…

Source插件之GstBaseSrc源码剖析

gst插件流程分析先看init构造函数gst_base_src_init&#xff08;本文流程主要基于filesrc插件讲解&#xff09; static void gst_base_src_init (GstBaseSrc * basesrc, gpointer g_class) {GstPad *pad;GstPadTemplate *pad_template;basesrc->priv gst_base_src_get_ins…

React:Redux

Redux引入 Redux感觉像组件通信的中介 state存放被管理的状态 action是申请改哪些数据&#xff0c;传入什么参数 reducer是怎么修改数据 我的理解更像是action像一个储存方法的对象&#xff0c;reducer是具体的方法的实现&#xff0c;不同的方法实现也不一样 store是个仓库…

CoDrivingLLM

CoDrivingLLM 思路 1.输入和输出 输入 算法的输入包括车辆当前时刻的状态 S t S_t St​ &#xff0c;这个状态包含了车辆的位置、速度、行驶方向等信息&#xff1b;以及参与协同驾驶的联网自动驾驶汽车列表C&#xff0c;用于确定需要进行决策的车辆集合。 输出 输出为车辆…

微信小程序接入deepseek

先上效果 话不多说&#xff0c;直接上代码&#xff08;本人用的hbuilder Xuniapp&#xff09; <template><view class"container"><!-- 聊天内容区域 --><scroll-view class"chat-list" scroll-y :scroll-top"scrollTop":…

xxxxx

从别人blog偷的~,自己复习用 爱奇艺 一面 介绍项目项目中用到的设计模式项目中如何处理高并发Redis集群Redis RDB 和 AOF原理Redis除了缓存和分布式锁还能干什么&#xff1f;数据库如何优化&#xff0c;索引的数据结构&#xff0c;多表联合如何优化RedissonAOP逻辑&#xff…

Yocto + 树莓派摄像头驱动完整指南

—— 从驱动配置、Yocto 构建&#xff0c;到 OpenCV 实战 在树莓派上运行摄像头&#xff0c;在官方的 Raspberry Pi OS 可能很简单&#xff0c;但在 Yocto 项目中&#xff0c;需要手动配置驱动、设备树、软件依赖 才能确保摄像头正常工作。本篇文章从 BSP 驱动配置、Yocto 关键…

总结(尚硅谷Vue3入门到实战,最新版vue3+TypeScript前端开发教程)

1.Vue简介 2020年9月18日&#xff0c;Vue.js发布版3.0版本&#xff0c;代号&#xff1a;One Piece 1.1.性能的提升 打包大小减少41%。 初次渲染快55%, 更新渲染快133%。 内存减少54%。 1.2.源码的升级 使用Proxy代替defineProperty实现响应式。 重写虚拟DOM的实现和Tree-Shak…

【五.LangChain技术与应用】【8.LangChain提示词模板基础:从入门到精通】

早上八点,你端着咖啡打开IDE,老板刚甩来需求:“做个能自动生成产品描述的AI工具”。你自信满满地打开ChatGPT的API文档,结果半小时后对着满屏的"输出结果不稳定"、"格式总出错"抓耳挠腮——这时候你真需要好好认识下LangChain里的提示词模板了。 一、…

基于编程语言的建筑行业施工图设计系统开发可行性研究————从参数化建模到全流程自动化的技术路径分析

基于编程语言的建筑行业施工图设计系统开发可行性研究————从参数化建模到全流程自动化的技术路径分析 文章目录 **基于编程语言的建筑行业施工图设计系统开发可行性研究————从参数化建模到全流程自动化的技术路径分析** 摘要引言一、技术可行性深度剖析1.1 现有编程语言…

【Linux文件操作篇】IO基础——被打开的文件,引入文件描述符

--------------------------------------------------------------------------------------------------------------------------------- 每日鸡汤&#xff1a;现实会告诉你&#xff0c;不努力就会被生活给踩死。无需找什么借口&#xff0c;一无所有&#xff0c;就是拼的理由…

Docker 学习(三)——数据管理、端口映射、容器互联

一、数据管理 容器中的管理数据主要有两种方式&#xff1a; 数据卷 &#xff08;Data Volumes&#xff09;&#xff1a; 容器内数据直接映射到本地主机环境&#xff1b; 数据 卷容器&#xff08; Data Volume Containers&#xff09;&#xff1a; 使用特定容器维护数据卷 1.…