为什么'获取/发布'不能保证c ++ 11中的顺序一致性?

时间:2018-05-21 01:50:08

标签: c++ c++11 atomic memory-barriers

-Thread 1-                
y.store (20, memory_order_release); 
x.store (10, memory_order_release);

-Thread 2-
if (x.load(memory_order_acquire) == 10) {
  assert (y.load(memory_order_acquire) == 20);
  y.store (10, memory_order_release)
}

-Thread 3-
if (y.load(memory_order_acquire) == 10) {
  assert (x.load(memory_order_acquire) == 10);
}

GCC Atomic Wiki段落“总结摘要”说,上面的代码assert(x.load(memory_order_acquire))可以失败。但我不明白为什么?

我的理解是:

  1. 由于获取障碍,Thread3无法 LoadLoad 重新排序。
  2. 由于发布障碍,Thread1无法 StoreStore 重新排序。
  3. 当Thread2读取(x) - > 10时,x必须从storebuffer刷新到Thread1中的缓存,因此每个线程都知道x已更改的值,例如缓存行无效。
  4. Thread3使用获取障碍,因此可以看到x(10)。

2 个答案:

答案 0 :(得分:2)

这是一个不好的例子,虽然它确实说明了心灵变形放松原子可以如何,我想。

[intro.execution] P9:

  

与a相关的每个值计算和副作用   full-expression在每个值计算和side之前排序   与要评估的下一个完整表达相关联的效果。

[atomics.order] P2:

  

对原子执行释放操作的原子操作 A   object M 与执行a的原子操作 B 同步   获取 M 上的操作并从中获取其中任何副作用的值   以 A 为首的发布序列。

因此,显示的评估通过先前排序和同步关系链接在一起:

Thread 1                   Thread 2              Thread 3

y.store(20)
   |
   | s.b.
   V           s.w.
x.store(10)  -------->  x.load() == 10
                               |
                               | s.b.
                               V      s.w.
                        y.store(10) --------> y.load() == 10
                                                  |
                                                  | s.b.
                                                  V
                                              x.load() == ?

因此链中的每个评估都在下一个之前发生(参见[intro.races] p9-10)。

[intro.races] P15,

  

如果原子对象 M 的值计算 A 发生在值之前    M 的计算 B A M 上的副作用 X 获取其值,   那么 B 计算的值应该是 X 或者存储的值   副作用 Y 存储在 M 上的值,其中 Y 跟随 Y    M 的修改顺序。

这里, A 是线程2中的值为10的负载, B 是线程3中的负载(在断言中)。由于 A 发生在 B 之前,并且x没有其他副作用, B 也必须读取10。

Herb Sutter有一个更简单的例子on his blog

T1: x = 1;
T2: y = 1;
T3: if( x == 1 && y == 0 ) puts("x first");
T4: if( y == 1 && x == 0 ) puts("y first");

您绝对需要顺序一致性以确保最多打印一行。

答案 1 :(得分:0)

您的示例是多线程程序的特例,该程序仅使用原子对象进行同步,而不使用具有明显熵的信息进行通信:唯一写入的值充当里程碑

这意味着任何商店:

  • 是释放操作
  • 传达一个值,该值指示一个线程的进度的精确点

该表单必须完全A.store (C, memory_order_release); 其中:

  • A是一个原子对象
  • C是一个常量

,一对(A,C)独特地代表了程序行。

相反,每次加载:

  • 是收购对象
  • 仅检查特定值

表格必须准确

if (A.load(memory_order_acquire) == C) { ... }

没有else子句的地方。

读取特定值表示进度,并确定所有先前的副作用(在特定程序点之前)均已发生。一小类程序永远不会具有有趣的多线程行为,因为一切都是进步的功能:里程碑已通过,并且行为必须纯粹是顺序的。