为什么需要memory order
- 编译器出于优化的目的,在编译阶段将源码的顺序进行交换。
- 程序执行期间,指令流水被CPU乱序执行。
- Cache的分层及刷新策略使得有时候某些写,读操作的顺序被重排。
- 原子操作说的是,一个操作的状态要么就是未执行,要么就是已完成,不会看见中间状态。
内存模型
内存模型所要表达的内容主要是这么描述: 一个内存操作的效果,在其他线程中的可见性问题
C++11 中的 atomic library 中定义了以下6种语义来对内存操作的行为进行约定,这些语义分别规定了不同的内存操作在其它线程中的可见性问题:
memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
};
- relaxed语义:当对公共变量以 relaxed 方式进行读写时,编译器,cpu 等是被允许按照任意它们认为合适的方式来加以优化处理的。
- release-acquire 语义:如果一个线程A对一块内存 m 以 release 的方式进行写操作,那么在线程 A 中,所有在该 release 操作之前进行的内存写操作,都在另一个线程 B 对内存 m 以 acquire 的方式进行读操作后,变得可见。
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<bool> ready{ false };
int data = 0;
void producer()
{
data = 100; // A
ready.store(true, std::memory_order_release); // B
}
void consumer()
{
while (!ready.load(std::memory_order_acquire)); // C
assert(data == 100); // never failed // D
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
-
首先 A 不允许被移动到 B 的后面。
-
同样 D 也不允许被移动到 C 的前面。
-
当 C 从 while 循环中退出了,说明 C 读取到了 B store()的那个值,此时,Thread-2 保证能够看见 Thread-1 执行 B 之前的所有写入操作(也即是 A)。
- memory_order_seq_cst语义:对所有以 memory_order_seq_cst 方式进行的内存操作,不管它们是不是分散在不同的 cpu 中同时进行,这些操作所产生的效果最终都要求有一个全局的顺序,而且这个顺序在各个相关的线程看起来是一致的。