释放-获取的传递性

Jee*_*enu 9 c++ memory-model memory-barriers stdatomic

正当我以为我已经掌握了原子知识时,我看到了另一篇文章。这是GCC wiki总体摘要下的摘录:

\n
 -Thread 1-       -Thread 2-                   -Thread 3-\n y.store (20);    if (x.load() == 10) {        if (y.load() == 10)\n x.store (10);      assert (y.load() == 20)      assert (x.load() == 10)\n                    y.store (10)\n                  }\n
Run Code Online (Sandbox Code Playgroud)\n
\n

释放/获取模式只需要所涉及的两个线程同步。这意味着同步值与其他线程不可交换。线程 2 中的断言仍然必须为 true,因为线程 1 和 2 与 x.load() 同步。线程 3 不参与此同步,因此当线程 2 和 3 使用 y.load() 同步时,线程 3 的断言可能会失败。线程 1 和 3 之间没有同步,因此不能为“x”假设任何值。

\n
\n

文章说线程 2 中的断言不会失败,但线程 3 中的断言可能会失败

\n

我觉得这很令人惊讶。这是我的推理链,线程 3 断言不会失败\xe2\x80\x94也许有人可以告诉我哪里错了。

\n
    \n
  1. 线程 3y == 10仅观察线程 2 写入 10 的情况。
  2. \n
  3. 仅当线程 2 看到 时才写入 10 x == 10
  4. \n
  5. 线程 2(或任何线程)x == 10仅看到线程 1 写入 10。任何线程都没有对 x 进行进一步更新。
  6. \n
  7. 由于线程 2 观察到x == 10,并且线程 3 也与线程 2 同步,因此应该观察x == 10
  8. \n
\n
\n

释放/获取模式只需要所涉及的两个线程同步。

\n
\n

有人可以指出这个仅限两方的要求的来源吗?我的理解(当然,也许是错误的)是生产者不知道它正在与谁同步。即,线程 1 不能说“我的更新仅适用于线程 2”。同样,线程 2 不能说“给我线程 1 的更新”。相反,线程 1 的发布x = 10可供任何人观察,如果他们愿意的话。

\n

因此,x = 10作为最后一次更新(由线程 1),任何从系统中任何地方发生的获取(通过传递同步确保)都保证观察到该写入,不是吗?

\n
\n

这意味着同步值与其他线程不可交换。

\n
\n

不管这是否属实,作者的意思可能是传递的,而不是交换的,对吧?

\n

最后,如果我上面错了,我很想知道什么同步操作可以保证线程 3 的断言不会失败。

\n

小智 8

看来您在 GCC wiki 中发现了一个错误。
\nT3 中的断言在 C++ 中不应失败。
\n以下是 C++20 标准中相关引用的解释:

\n
    \n
  1. x.store (10)T1 中的事件发生 assert (x.load() == 10)在 T3 之前,因为:\n
      \n
    • 每个线程中的语句都按照before 排序
    • \n
    \n
    \n

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

    \n
    \n
      \n
    • x.store (10) 与 同步 if (x.load() == 10)y.store (10) 与 同步 if (y.load() == 10)
    • \n
    \n
    \n

    2对原子对象 M 执行释放操作的原子操作AM执行获取操作的原子操作B同步,并从以A为首的释放序列中的任何副作用中获取其值。

    \n
    \n
      \n
    • 结果x.store (10) 线程间发生在之前 assert (x.load() == 10)
    • \n
    \n
    \n

    9如果\n (9.1) \xc2\xa0 \xe2\x80\x94 A与B同步,或者\n (9.2) \xc2\xa0 \xe2\x80\x94 A ,则评估A 线程间发生在评估B之前依赖顺序先于B,或\n (9.3) \xc2\xa0 \xe2\x80\x94 进行某些评估X \n (9.3.1) \xc2\xa0\xc2\xa0 \xe2\x80\x94 A同步XX在B之前排序,或\n (9.3.2) \xc2\xa0\xc2\xa0 \xe2\x80\x94 A在XX线程间发生在B之前排序,或\n (9.3 .3) \xc2\xa0\xc2\xa0 \xe2\x80\x94 A线程间发生在X之前,并且X线程间发生在B之前。





    \n
    \n
      \n
    • 这也意味着x.store (10) 发生在之前 assert (x.load() == 10)
    • \n
    \n
    \n

    10计算A 发生在计算B之前(或者,等效地, B发生在A之后),如果:
    \n (10.1) \xc2\xa0 \xe2\x80\x94 A在B之前排序,或者
    \n (10.2) \xc2 \xa0 \xe2\x80\x94 A线程间发生在B之前。

    \n
    \n
  2. \n
  3. 上述意味着x.load()inassert (x.load() == 10)必须返回10由 编写的x.store (10)
    \n(我们假设 已x正确发布,因此 的初始值在 的修改顺序中x排在前面)。\nx.store (10)x
    \n

    18如果原子对象M上的副作用X发生在M的值计算B之前,则评估B应从X或按照M的修改顺序从X后面的副作用Y获取其值。\n[注 18:此要求称为写读一致性。\xe2\x80\x94 尾注]

    \n
    \n
  4. \n
\n