简介
编译器的优化会产生乱序指令,cpu也会因为指令流水线优化产生乱序指令。当然这些乱序指令都是为了同一个目的,优化执行效率 。
这里解释下两个词的含义:
- happens-before:按照程序的代码序执行
- synchronized-with:不同线程间,对于同一个原子操作,需要同步关系,store操作一定要先于 load,也就是说 对于一个原子变量x,先写x,然后读x是一个同步的操作,读x并不会读取之前的值,而是写x后的值。
C++11有6种内存序
- std::memory_order_relaxed(__ATOMIC_RELAXED)
- std::memory_order_acquire (__ATOMIC_ACQUIRE)
- std::memory_order_release (__ATOMIC_RELEASE)
- std::memory_order_acq_rel (__ATOMIC_ACQ_REL)
- std::memory_order_consume(__ATOMIC_CONSUME)
- std::memory_order_seq_cst (__ATOMIC_SEQ_CST)
详解
memory_order_relaxed
不对执行顺序做保证,没有happens-before的约束,编译器和处理器可以对memory access做任何的reorder,这种模式下能做的唯一保证,就是一旦线程读到了变量var的最新值,那么这个线程将再也见不到var修改之前的值了。
同时当relaxed存一个数据的时候,另外的线程将需要一个时间才能relaxed读到该值,在非缓存一致性的构架上需要刷新缓存。在开发的时候,如果你的上下文没有共享的变量需要在线程间同步,选用Relaxed就可以了。
举个例子说明一下:
对于最初为零的 x 和 y
// 线程 1 :
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// 线程 2 :
r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D
允许产生结果 r1 == 42 && r2 == 42,执行的顺序可能为D A B C。
memory_order_acquire 和memory_order_release
memory_order_acquire保证本线程中,所有后续的读操作必须在本条原子操作完成后执行。memory_order_release保证本线程中,所有之前的写操作完成后才能执行本条原子操作。
通常的做法是:将资源通过store+memory_order_release的方式”Release”给别的线程;别的线程则通过load+memory_order_acquire判断或者等待某个资源,一旦满足某个条件后就可以安全的“Acquire”消费这些资源了。即释放获得顺序。
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // 绝无问题
assert(data == 42); // 绝无问题
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
memory_order_acq_rel则是同时包含memory_order_acquire 和 memory_order_release
释放消费顺序(release/consume)的规范正在修订中,而且暂时不鼓励使用 memory_order_consume 。
memory_order_seq_cst
该模型是最强的同步模式,同时也是默认的模型,具有强烈的happens-before语义,
来保证指令的顺序一致执行,相当于不打开编译器优化指令,按照正常的指令序执行。多线程各原子操作也会Synchronized-with,譬如atomic::load()需要等待atomic::store()写下元素才能读取。更进一步地,读操作需要在“一个写操作对所有处理器可见”的时候才能读,适用于基于缓存的体系结构。
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x(){
x.store(true, std::memory_order_seq_cst);
}
void write_y(){
y.store(true, std::memory_order_seq_cst);
}
void read_x_then_y(){
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
void read_y_then_x(){
while (!y.load(std::memory_order_seq_cst))
;
if (x.load(std::memory_order_seq_cst)) {
++z;
}
}
int main(){
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // 决不发生
}