[C++]从入门到精通-atomic详解

什么是atomic特性?

atomic的本质是利用计算机CPU的一些特性,来在内存空间中开辟一块独特的空间,能够在多线程环境提供安全的数据访问,不需要再使用互斥锁, 这样能降低性能损耗。 PS: 互斥锁是操作系统层面同步机制,并且涉及到进程切换,相当于CPU上的寄存器需要和内存或者磁盘来回交互才能进行互斥切换,而atomic 是在cpu中进行实现,也就是只有在寄存器粒度上进行切换。

使用方法

常用的使用方法如下

cpp 复制代码
#include <atomic>

// 声明原子类型
std::atomic<int> atomic_int(0);
std::atomic<bool> atomic_bool(false);

// 基本操作
atomic_int.store(1);  // 存储值
int value = atomic_int.load();  // 读取值
atomic_bool.exchange(true);  // 交换值: 将atomic_bool 设置成true,并返回当前的值

原理

介绍内存序之前需要介绍一些前序知识,即 CPU的流水线机制 流水线机制:取指令(Fetch) -> 解码(Decode) -> 执行(Execute) -> 访存(Memory) -> 写回(Write Back) 具体可以参考文档: todo: XXXX 内存序主要作用在 访存和写回这两个阶段

  1. 访存:处理内存读写操作
  2. 写回: 将指令的执行结果写回到寄存器或内存 本质上内存序就是控制的Store Buffer这一块在多线程环境下的写入和写出逻辑,本质上是为了对抗计算机的乱序执行,在计算机中程序最后被执行的顺序,并不全是代码编写的顺序。

atomic 的内存模型

主要分为以上这6种内存序,接下来详细说说这些内存序的同步保证

cpp 复制代码
std::atomic<int> x{0};
std::atomic<int> y{0};

// Thread 1
x.store(1, 内存序); // Store 1
y.store(2, 内存序); // Store 2

// Thread 2
int r1 = y.load(内存序); // Load 1
int r2 = x.load(内存序); // Load 2

内存序=memory_order_relaxed

本质上就是随读随写,写入之后,可以允许立刻读,但是不同的线程种可能存在缓存,可能会存在读取的值和实际的值不一致的情况,接下来举个具体的例子,本质上,计算机编程就是对各种各样的存储器和网卡进行编程。

因为上面的例子是std::memory_order_relaxed 在计算机中的执行示意图如下 指令可能会被随意排序导致如下的情况,至于为什么会任意排序,是因为计算机中的乱序执行,具体可以参考: todo r1 =2 r2= 1 r1=1 r2 = 2 r1 =1 r2 = 1 r2 =2 r2 =2

内存序=memory_order_acquire

注意:只能使用在load方法上。相当于所有的load方法读取内存数据的时候都会等待其他的写入操作完成即如下示意图: 注意点: 保证该load之后的所有内存操作不会被重排到这个load之前

内存序= memory_order_release

根据上面的架构,会把内存强行推入到缓存系统中,这样能够被整个系统中共享 release操作会强制刷新之前的所有Store Buffer内容,就会在后续的load方法也就是,访存中读取到所有的修改,本质上内存序就是为了对抗多级缓存带来的数据不一致以及乱序执行

内存序= memory_order_acq_rel

相当于 memory_order_release+memory_order_acquire, 但是注意只有部分场景能够直接使用

cpp 复制代码
// memory_order_acq_rel 只能用于读-改-写(RMW)操作:
atomic<int> x{0};

// 这些可以使用 memory_order_acq_rel:
x.fetch_add(1, memory_order_acq_rel);
x.exchange(2, memory_order_acq_rel);
x.compare_exchange_strong(old_val, new_val, memory_order_acq_rel);

在exchange之前的所有写入对其他线程可见

内存序=memory_order_seq_cst

memory_order_seq_cst 相当于在memory_order_acq_rel 的基础上,加上一个全局特性。 就是相当于一块内存在所有的线程中看到的变化都一样,比如说A从1到2再到3 换到另一个线程的观测也是相同的。但其实一般情况下也用不上,一般使用memory_order_acq_rel内存序就能解决了。

内存序=memory_order_consume

memory_order_consume 这个是指针粒度,也就是内存的地址粒度维护了一个血缘关系, 本质上就是相当于使用当前的这个atomic数据,就能通过内存地址维护的血缘知道之前进行过哪些操作,拿到最终的变化,但是保证不了同时期的一些变量,比如同一个局部空间中的变量的情况, 只会保证 ptr.store(p) 中的p的变化是能够正常可见的,但是global_data就不确定了

总结

内存序就是用来控制这三个层面的乱序

  1. CPU指令重排
  2. 编译器优化重排
  3. 内存系统的可见性顺序(多级缓存)

PS: 更详细的可以上claude问AI,不建议直接看书,太慢

猜你喜欢

C++多线程blog.csdn.net/luog_aiyu/a... 一文了解LevelDB数据库读取流程blog.csdn.net/luog_aiyu/a... 一文了解LevelDB数据库写入流程blog.csdn.net/luog_aiyu/a...

PS

你的赞是我很大的鼓励 欢迎大家加我飞书扩列, 希望能认识一些新朋友~ 二维码见: www.cnblogs.com/DarkChink/p...

相关推荐
寻月隐君8 分钟前
【Solana 开发实战】轻松搞定链上 IDL:从上传到获取全解析
后端·web3·github
程序员爱钓鱼18 分钟前
Go项目上线部署最佳实践:Docker容器化从入门到进阶
后端·google·go
汪子熙19 分钟前
Visual Studio Code 中排除指定文件夹搜索的最佳实践与实现原理
后端·面试
大P哥阿豪1 小时前
Go defer(二):从汇编的角度理解延迟调用的实现
开发语言·汇编·后端·golang
风象南1 小时前
SpringBoot 与 HTMX:现代 Web 开发的高效组合
java·spring boot·后端
wstcl2 小时前
让你的asp.net网站在调试模式下也能在局域网通过ip访问
后端·tcp/ip·asp.net
ai小鬼头10 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
萧曵 丶10 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
老任与码11 小时前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
华子w90892585911 小时前
基于 SpringBoot+VueJS 的农产品研究报告管理系统设计与实现
vue.js·spring boot·后端