彻底理解C++内存序
前言
c++官网的介绍: https://en.cppreference.com/w/cpp/atomic/memory_order
C++提供的原子变量api,基本都会提供一个入参选项,可以填内存序。很早之前我就学习了内存模型的相关概念,但是一直没有系统的学习和整理C++应该怎么用这些内存序。
首先直接给个总结
- 绝大多数程序逻辑只需要acq、rel和relaxed的内存序即可
- releaxed的内存用于对该变量读写值不关心的情况,比如读写顺序彻底乱排也不影响程序逻辑,比如只是用来计数统计结果
- seq_cst内存序的使用场景为,当存在多个线程和多个原子变量,并且程序逻辑强依赖于多个原子变量的读写时,需要全局存在一个原子变量读写顺序时,才使用该内存序。
- 其他情况都用acq或rel或两者结合的内存序即可。
C++内存序
C++内存模型
定义: 程序的可能执行顺序
六种内存序,保证了四种内存模型,其中consumer模型已经弃用
- 顺序一致性模型:我们写代码的顺序一样,内存操作不会发生乱序,C++默认模型
- Acquire-release模型: Acquire语义保证load之后的读写操作不会重排到load之前,Release语义保证store之前的读写操作不会重排到store之后,为X86架构默认的内存模型,即X86只支持Store-load之间的读写指令重排,是一种强模型
load 从内存中取值到寄存器, store将寄存器中的值送回内存
- Relaxed语义,几乎能进行一切指令重排(当然是无关变量之间比如 c= b, d= a就可以重排,X86就不会),Arm架构默认的内存模型。因此程序员可能需要手动通过内存模型或者C++的内存序保证执行的顺序。
一共定义了六个内存序,其中consume
没见过,弃用。
1 |
|
acquire与release内存序
以下例子即用了该内存序实现,不允许使用relaxed内存序,多个线程逻辑顺序由原子变量的读写过程控制。
1 |
|
以上x绝不可能等于3,即先store了,函数2通行后y=2,函数1再执行x = 2 + 1为3
。如果使用releaxed内存序就有可能发生。
store操作确保了其前面的读写,即x=y+1
无法排到其后面。而acquire操作确保了y的赋值一定在load后做。因为release不允许前面的指令排其后,acquire不允许其后面的指令排其前。
seq_cst 内存序
之前介绍的acq_rel内存序基本能解决2个线程之间的同步需求了。只有比较极端的逻辑场景可能需要全局内存序,全局内存序的性能相比其他内存序是很差的。
参考stack_overflow的问题:https://stackoverflow.com/questions/12340773/how-do-memory-order-seq-cst-and-memory-order-acq-rel-differ
知乎也有相关回答:https://www.zhihu.com/question/8811713845/answer/75716283973?utm_psn=1874990550719004672
这里直接摘抄c++官方的例子,该例子必须要全局序才能实现逻辑。如果用acq_rel的话assert(z.load() != 0)
可能失败,z可能等于0。因为存在情况,C线程认为x先store再ystore,而D线程相反。
1 |
|