各种顺序名词含义
1,背景
ART虚拟机中(安卓run time)使用futex/atomic实现了自己的Mutex/Condition类(art/runtime/base/mutex.h/mutex.cc),其中使用了AtomicInteger(std::atomic)的封装。要想了解Mutex类的实现原理是,需要搞清楚什么是c++ memory order。弄懂c++ memory order也是必须的。
2,什么是memory order
Memory order我认为是目前计算机编程里面比较晦涩难懂的概念之一, 始终一知半解,不能理解其本质,趁此机会再继续学习一下,有些理解不正确的地方,欢迎大家指正。介绍memory order的文章非常多,随便在知乎/Google都可以搜索到一些文章,但是不少写的也很概念模糊,不同作者知识背景和所占的角度差异,其实无助于我自己更好的理解。为了更符合我个人认知逻辑,决定从两方面认识memory order:
一是memory order的Cognitive Architecture梳理,弄清楚核心概念及其为什么引入这些概念,这些概念解释尽量来源于经典著作
二是从实际代码场景剖析,为什么这些实现代码要使用memory order。因为memory order是一个广泛的概念,存在在不同的编程语言中(如java/c/c++ etc),也存在OS Kernel中。这里主要介绍C++ Memory Order Model,不过很多概念应该是相同的
2.1 Cognitive Architecture
我们先通过梳理各种基础概念及其之间的关系,便于后续的理解。
Memory ordering: describes the order of accesses to computer memory by a CPU.(来自wiki),即CPU执行指令访问内存的顺序。
先给个知识大纲 c++ memory order
- CPU指令乱序,CPU cache一致性,Compiler优化
- Memory Order Model 用于保证指令执行order
- Relaxed ordering
- Release-Acquire ordering
- Release-Consume ordering
- Sequentially consistent ordering
- Order
- evaluation order
- sequenced-before
- carries dependency
- CPU order
- Release sequence
- Modification order
- evaluation order
synchronizes-with关系,simple happens-before,inter-thread happens before,dependency-ordered before,coherence rule(WW,RW,RR,WR),以及一般的happens-before
1,为什么引入memory order ?
“ source-level访问内存”,“machine指令-level访问内存“,“CPU-level及其Cache访问可见性”。三个level层面(程序员,编译器,CPU)看到的内存访问顺序的不一致,是引入内存序的原因。编译器会重排指令,CPU支持乱序执行,多级Cache的导致各个CPU对内存访问的不可见,这些因素导致source源码层面的读写内存顺序,跟真正CPU层面看到的执行看到的内存顺序是不一致的。
如:Thread1对变量a,b进行赋值,Thread2对变量a,b进行读操作:尽管源代码上写内存的顺序是a=1,b=2,但是对另外一个线程来说看到顺序并不一定先读到a,再是b。
Thread1 Thread2
int a = 1 if (b == 2)
int b = 2 assert(a==1);
日常开发程序中,大部分程序员是不需要意识到memory order的概念的,使用mutex等并发编程模型即可。但在Kernel,虚拟机,编译器及其基础库,无锁设计的系统编程中,会用到memory order概念,所以了解这个概念是必要的。
2.2,主要概念
- Memory Order:
访问内存的顺序,既然是顺序必然涉及多个内存地址(全局变量,共享的Heap变量等)访问,大部分memory order牵涉的是多线程环境,因此Memory Order是在多个线程多个共享内存访问配合完成一定的事情需要考虑的问题。
- Evaluation of Expressions:
下面会经常看到Evaluation ,Evaluation即对表达式的进行的计算,包括:
value computations:计算表达式的返回值
side effects评估:包括是否读写一个volatile对象;是否修改一个对象;调用一个io函数等。
- Evaluation Order:
编译器对表达式的Evaluation 顺序,这个顺序往往是不确定未定义的行为。
#include <cstdio>
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}
int main() {
z(a(), b(), c()); // all 6 permutations of output are allowed
return a() + b() + c(); // all 6 permutations of output are allowed
}
//即不知道编译器产生的最终代码的调用顺序,上面评估序是六中可能。
- Sequenced-before关系
即同一个线程视角,看到的不同evaluation之间的前后执行顺序,具有asymmetric(非对称), transitive(传递性), pair-wise(配对)。A和B是两个不同的Evaluation:
-
A is sequenced before B:B在Evaluation的时候,A的Evauation必须完成
-
B is sequenced before A:同上替换
A和B没有sequenced before关系:即之间无任何关系,Evaluation动作可以交叉,也可以前,也可以后。
- Carries dependency关系(主要用于Consume-Release Model)
首先A is sequenced before B,并且满足如下条件:
-
A是B表达式中的一个操作数,除了一下情况
-
if B is a call to std::kill_dependency
-
if A is the left operand of the built-in
&&
,||
,?:,
or,
operators.
-
-
A对一个scalar object M进行修改, B读取M值
-
关系传递:A依赖X,X依赖B,则B依赖A
- Synchronizes-with关系
Synchronizes-with is a term invented by language designers to describe ways in which the memory effects of source-level operations – even non-atomic operations – are guaranteed to become visible to other threads.
- Consume operation:
Atomic load with memory_order_consume or stronger is a consume operation.
- Acquire operation
Atomic load with memory_order_acquire or stronger is an acquire operation.
- Release operation
Atomic store with memory_order_release or stronger is a release operation.
- Release sequence
Release sequence headed by A:该序列由A开始,当前线程内对M的write操作,以及其他线程对M的read-modify-write操作组成。
- Dependency-ordered before
一个线程完成A对M对象Release操作,另外一个线程B对M对象完成Consume操作,B能够读取到A的Release sequence序列中的任意一个值。
- Inter-thread happens-before
Between threads, evaluation A inter-thread happens before evaluation B if any of the following is true:
-
A
synchronizes-with
B -
A is
dependency-ordered
before B -
A
synchronizes-with
some evaluation X, and X issequenced-before
B -
A is sequenced-before some evaluation X, and X inter-thread happens-before B
-
A inter-thread happens-before some evaluation X, and X inter-thread happens-before B
- Happens-before
Regardless of threads, evaluation A happens-before evaluation B if any of the following is true:
- A is
sequenced-before
B - A
inter-thread
happens before B
2.3,Memory Order Model
上面介绍了很多概念,基本都是在说evaluation 可能存在的关系。为什么要说这些关系,c++ memory order模型目的就是帮助开发人员构建关系,保持代码能够按照预期的执行顺序进行。
- Relaxed ordering:
这个是最简单的模型,不保证任何的关系,只保证原子性和可见性。典型应用incrementing counters,并发环境工作中不涉及到多个线程多个共享变量的同步场景。std::memory_order_relaxed:There are no synchronization or ordering constraints imposed on other reads or writes, only this operation’s atomicity is guaranteed
- Release-Consume ordering
目前很少用,暂不了解
- Release-Acquire ordering
借用网上的图,我觉得写的非常形象:
Release操作:RELEASE操作前所有的READ/WRITE都不能跑到store后面
a=1;------| ^
v1=b;--V V |^
------------- ||
store(&ready,1,RELEASE);||
c=1;---------------------|
v2=d;---------------------
release前面的操作被屏障隔断了,无法跑到后面去,但release后面的操作却可以跑到前面去。
Acquire操作:acquire后面都读写操作无法排到前面
a=1;----------------------|
v1=b;-------------------- |
------------------ | |
load(&ready, ACQUIRE);| |
c=1;------^ V V
v2=d;-----^
acuire后面的操作被屏障隔断了,无法跑到前面,但是前面的操作却可以跑到后面来。
注意:acuqire/release操作是必须配对使用,否则是无意义的
-
Sequentially-consistent ordering
即两边的read/load都无法穿过。
a=1;---------|
v1=b;------V V
------------------
fas(&ready,1,SEQ_CST);
c=1;------^
v2=d;-----^
3,案例说明
3.1 Relaxed模型
比较简单,常用于计数场景,不需要多个变量进行配合,就可以完成一定的任务。
3.2 Release-Acquire ordering
https://androidxref.com/9.0.0_r3/xref/external/skia/include/private/SkSpinlock.h
SpinLock的实现:Acquire/Release保证所有被保护的内存操作出现在Acquire/Release操作中间。
3.3 Sequentially-consistent ordering
后续补充合适的例子
4,参考资料
- https://en.cppreference.com/w/cpp/atomic/memory_order
- https://en.cppreference.com/w/c/language/eval_order
- https://en.wikipedia.org/wiki/Memory_ordering
- https://preshing.com/20130823/the-synchronizes-with-relation/
- https://mariadb.org/wp-content/uploads/2017/11/2017-11-Memory-barriers.pdf