整理自 CPP Concurrency In Action 5.3 Synchronizing operations and enforcing ordering(同步操作和强制排序)
c++11有六个内存序选项可应用于原子类型的操作:memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cst。
代表三种内存模型:顺序一致性(sequentially consistent),获取-释放序(memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel)和自由序(memory_order_relaxed)。
Store操作,可选如下内存序:memory_order_relaxed, memory_order_release, memory_order_seq_cst。
Load操作,可选如下内存序:memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_seq_cst。
Read-modify-write操作,可选如下内存序:memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst。
极不推荐使用memory_order_consume。个人不推荐使用非默认序,很难考虑周全,很难测试,效率也不见得有明显提升。
默认使用顺序一致性,顺序一致性中,程序中的行为从任意角度去看,序列都保持一定顺序。每个线程看其他线程操作的顺序都是一样的,比较符合正常思维。
看一个顺序一致性的例子。(后面例子中全局变量x,y,z及其初值均与该例相同,每个函数都是一个线程)
std::atomic<bool> x{false}, y{false};
std::atomic<int> z{0};
// thread 1
void write_x()
{
x = true; // 1
}
// thread 2
void write_y()
{
y = true; // 2
}
// thread 3
void read_x_then_y()
{
while(!x);
if(y) ++z; // 3
}
// thread 4
void read_y_then_x()
{
while(!y);
if(x) ++z; // 4
}
因为序列要保持一定顺序,就能比较简单地分析出所有可能的情况。可以确定的是,1一定在3之前,2一定在4之前,那排列组合左右可能的序列有:2413,2143,2134,1243,1234,1324。z的值分别为1,2,2,2,2,1。
自由序中,同一线程中对于同一变量的操作还是遵从先行关系,但别的线程看来就不一定了。自由序中唯一要求是同一线程中同一原子变量的访问不能乱序。
为了了解自由序是如何工作的,可先将每一个变量想象成在一个独立房间中拿着记事本的人。他的记事本上是一组值的列表,可以通过打电话的方式让他给你一个值,或让他写下一个新值。如果告诉他写下一个新值,他会将这个新值写在表的最后。如果让他给你一个值,他会从列表中读取一个值给你。
第一次与这人交谈时,如果问他要一个值,他可能会在现有的列表中选取任意值告诉你。
如果之后再问他要一个值,可能会得到与之前相同的值,或是列表中之前值下端的其他值,他不会给你上端的值。
如果让他写一个值,并且随后再问他要一个值,他要不就给你你刚告诉他的那个值,要不就是一个列表中那个值下端的值(别人告诉他写下的值)。
“他”就是使用自由序的原子变量。
多个使用自由序的原子变量,每一个都拥有自己的修改顺序,但是他们之间没有任何关系。
可一个自由序的例子:
void write_x_then_y()
{
x.store(true, std::memory_order_relaxed); // 1
y.store(true, std::memory_order_relaxed); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); // 3
if(x.load(std::memory_order_relaxed)) // 4
++z;
}
线程1看来,x、y依次置为true,但在线程2看来,y为true时,x可能为false。
获取-释放序是自由序的加强版,虽然操作依旧没有统一顺序,但引入了同步。
将上面的例子改成:
void write_x_then_y()
{
x.store(true, std::memory_order_relaxed); // 1
y.store(true, std::memory_order_release); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_acquire)); // 3
if(x.load(std::memory_order_relaxed)) // 4
++z;
}
2同步于3,1先于2,3先于4,所以1先于4,z为1。
将顺序一致性中的例子修改下:
void write_x()
{
x.store(true, std::memory_order_release); // 1
}
void write_y()
{
y.store(true, std::memory_order_release); // 3
}
void read_x_then_y()
{
while(!x.load(std::memory_order_acquire)); // 2
if(y.load(std::memory_order_acquire)) // 5
++z;
}
void read_y_then_x()
{
while(!y.load(std::memory_order_acquire)); // 4
if(x.load(std::memory_order_acquire)) // 6
++z;
}
1同步于2,但在5那,y不一定是true;3同步于4,在6那,x不一定为true。z可能为0,1,2。