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

什么是atomic特性?

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

使用方法

常用的使用方法如下

#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种内存序,接下来详细说说这些内存序的同步保证

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, 但是注意只有部分场景能够直接使用

// 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的变化是能够正常可见的,但是global_data就不确定了

总结

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

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

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

猜你喜欢

C++多线程: https://blog.csdn.net/luog_aiyu/article/details/145548529
一文了解LevelDB数据库读取流程:https://blog.csdn.net/luog_aiyu/article/details/145946636
一文了解LevelDB数据库写入流程:https://blog.csdn.net/luog_aiyu/article/details/145917173

PS

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值