现代C++的内存模型。–神文
自底向上理解memory_order
大白话C++之:一文搞懂C++多线程内存模型(Memory Order)

时钟周期也称为振荡周期,定义为时钟频率的倒数。时钟周期是计算机中最基本的、最小的时间单位。在一个时钟周期内,CPU仅完成一个最基本的动作。时钟周期表示了SDRAM所能运行的最高频率。

如果没有Cache,CPU每执行一条指令,都要去内存取下一条,而执行一条指令也就几个时钟周期(几ns),而取指令却要上百个时钟周期,这将导致CPU大部分时间都在等待状态,进而导致执行效率低下。

C++ 内存模型(Memory Model)定义了程序在多线程环境中如何访问和共享内存,它为程序的正确性、并发性和可移植性提供了保证。C++ 内存模型主要通过原子操作、内存序列(Memory Ordering)、同步和锁等机制来规范线程之间的内存访问行为。


CPU 的五级流水线

CPU 将指令执行分解成5个部分,分别是:IF 取指令,ID 译码,EX 执行,MEM 访问内存,WB 写回。

内存顺序模型描述
memory_order_seq_cst顺序一致(sequentially consistent ordering),只有该值满足sC顺序一致性,原子操作默认使用该值。
memory_order_relaxed松散(relaxed ordering)
memory_order_consume获取发布(acquire-release ordering)
memory_order_acquire获取发布(acquire-release ordering)
memory_order_release获取发布(acquire-release ordering)
memory_order_acq_rel获取发布(acquire-release ordering)

与编译器优化有关:

1
2
3
4
5
6
7
//reordering 重排示例代码
int A = 0, B = 0;
void foo()
{
    A = B + 1;  //(1)
    B = 1;      //(2)
}
1
2
3
4
5
6
7
8
// g++ -std=c++11 -O2 -S test.cpp
// 编译器重排后的代码
// 注意第一句汇编,已经将B最初的值存到了
// 寄存器eax,而后将该eax的值加1,再赋给A
movl  B(%rip), %eax
movl  $1, B(%rip)          // B = 1
addl  $1, %eax             // A = B + 1
movl  %eax, A(%rip)
1
2
3
4
5
6
7
8
9
// Invention示例代码
// 原始代码
if( cond ) x = 42;

// 优化后代码
r1 = x;// read what's there
x = 42;// oops: optimistic write is not conditional
if( !cond)// check if we guessed wrong
    x = r1;// oops: back-out write is not SC

对于内存读写来说,读写顺序需要严格按照代码顺序,即要求如下(符号<p表示程序代码顺序,符号<m表示内存的读写顺序):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 顺序一致的要求
/* Load→Load */
/*若按代码顺序,a变量的读取先于b变量,
则内存顺序也需要先读a再读b
后面的规则同理。*/
if L(a) <p L(b)  L(a) <m L(b)

/* Load→Store */
if L(a) <p S(b)  L(a) <m S(b)

/* Store→Store */
if S(a) <p S(b)  S(a) <m S(b)

 /* Store→Load */
if S(a) <p L(b)  S(a) <m L(b)

顺序一致这么严格,其显然会限制编译器和CPU的优化,所以业界提出了很多宽松的模型,例如在X86中使用的TSO(Total Store Order)便允许某些条件下的重排。

memory_order_acquire:对于使用该枚举值的load操作,不允许该load之后的操作重排到load之前。
memory_order_release:使用该枚举值的store操作,不允许store之前的操作重排到store之后。

现代C++(包括Java)都是使用了SC-DRF(Sequential consistency for data race free)。在SC-DRF模型下,程序员只要不写出Race Condition的代码,编译器和CPU便能保证程序的执行结果与顺序一致相同。因而,内存模型就如同程序员与编译器/CPU之间的契约,需要彼此遵守承诺。C++的内存模型默认为SC-DRF,此外还支持更宽松的非SC-DRF的模型。

C++内存模型借鉴lock/unlock,引入了两个等效的概念,Acquire(类似lock)和Release(类似unlock),这两个都是单方向的屏障(One-way Barriers: acquire barrier, release barrier)。