C++11中引入了六种Memory Order,但是这个不是C++的首创,主要用途是应用于原子操作。
enum memory_order{
memory_order_relaxed, //允许任意重排
memory_order_consume, //别用
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst //默认
}
一个简单的导致race condition
的例子
int x = 0;
//thread 1
x = 100;
//thread 2
std::cout<<x<<std::endl; //what is x ?
这个条件中可能x是0,可能是100。 可能的原因有多个:
thread1
落后于thread2
执行,thread2
看到的x=0
.thread1
在cpu0
上先于thread2
执行,thread2
在cpu1上执行,虽然它比thread1慢,但是直接从缓存里取了x=0
.
Relax mode
std::memory_order_relaxed
是最宽松的内存模型,无任何同步要求,只保证对原子变量的修改是原子的,允许编译器任意重排指令。
// 线程 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 == r2 == 42
,因为在编译器和CPU的乱序执行的共同作用下,可能执行的顺序为D->A->B->C
。
Relax约束最少,适合作为无依赖的原子变量使用,比如单独的引用计数。
Acquire-Release
对同一原子变量的Acquire-Release
操作,将会影响到修改原子变量之前和之后的读写顺序。简单地说,在线程1中Release
操作之前发生的所有store
操作,在线程2Acquire
之后都保证可见。
还是拿cppreference里例子。
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); // 绝无问题
}
虽然这里的原子变量只有ptr
,但是在ptr
的release操作之前,对int data
的写入操作, 对于consumer
acquire后的两个assert一定是可见的。
Acquire-Release
还具有传递性,比如来自cppreefrence
的另外一个例子.
void thread_1()
{
data.push_back(42);
flag.store(1, std::memory_order_release);
}
void thread_2()
{
int expected=1;
while (!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) {
expected = 1;
}
}
void thread_3()
{
while (flag.load(std::memory_order_acquire) < 2)
;
assert(data.at(0) == 42); // 决不出错
}
这里的关键在于thread2
中的acq_rel
操作,它确保了data.push_back(42)
一定发生在compare_exchange_strong
之前。
Release-Consumer
简单的说,只确保原子变量及其依赖的读写是可见的,Release-Acquire
中举的第一个例子,可能会出错,因为不保证data的读写一定能看到。
根据cppreference, The specification of release-consume ordering is being revised, and the use of memory_order_consume is temporarily discouraged.
请直接忽视这个语义。
Sequentially-consistent order
加强版的Acq-Rel,要求所有线程的指令都按照源代码的书写顺序来执行,不允许重排,Acq-Rel
有的性质它都有。