6
-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段落“Overall Summary”说,上面的代码assert(x.load(memory_order_acquire))可能会失败。但我不明白为什么?

我的理解是:</p>

  1. 由于获取障碍, Thread3无法LoadLoad 重新排序。
  2. 由于释放障碍,Thread1无法StoreStore重新排序。
  3. 当 Thread2 read(x)->10 时,x 必须从 storebuffer 刷新到 Thread1 中的缓存,所以每个线程都知道 x 的值发生了变化,例如使缓存行无效。
  4. Thread3 使用Acquire屏障,因此它可以看到 x(10)。
4

2 回答 2

2

这是一个不好的例子,尽管我想它确实说明了放松原子是多么令人心烦意乱。

[intro.execution] p9:

与完整表达式关联的每个值计算和副作用在与要评估的下一个完整表达式关联的每个值计算和副作用之前排序。

[atomics.order]p2:

对原子对象M执行释放操作的原子操作A与对M执行获取操作的原子操作B同步,并从以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存储的值或存储的值通过对M的副作用Y,其中Y按照M的修改顺序跟随X。

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


Herb Sutter在他的博客上有一个更简单的例子:

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

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

于 2018-05-21T05:58:10.560 回答
0

您的示例是多线程程序的特殊情况,它仅使用原子对象进行同步,而不是用于以显着熵传递信息:写入的唯一值充当里程碑

这意味着任何商店:

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

表格必须准确地位于A.store (C, memory_order_release);

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

并且这对(A,C)独特地表征了程序线。

相反,每个负载:

  • 是收购
  • 仅检查特定值

表格必须准确

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

没有 else 子句的地方。

读取特定值表示进度并确定所有先前的副作用(在特定程序点之前)都已发生。那小类程序从来没有有趣的多线程行为,因为一切都是进度的函数:里程碑已经过去,行为必须是纯顺序的。

于 2019-11-23T00:32:53.613 回答