memory_order_seq_cst 栅栏在 C++20 中有何用处?

How are memory_order_seq_cst fences useful anymore in C++20?

考虑这段代码:

std::atomic<int> x{ 0 };
std::atomic<int> y{ 0 };
int a;
int b;

void thread1()
{
    //atomic op A
    x.store(1, std::memory_order_relaxed);

    //fence X
    std::atomic_thread_fence(std::memory_order_seq_cst);
    //sequenced-before P, thus in SC order X=>P

    //atomic op P
    a = y.load(std::memory_order_seq_cst);//0
    //reads-before(from-read) Q, thus in SC order P=>Q
}

void thread2()
{
    //atomic op Q
    y.store(1, std::memory_order_seq_cst);
    //sequenced-before B, thus in SC order Q=>B

    //atomic op B
    b = x.load(std::memory_order_seq_cst);
}

int main()
{
    std::thread t2(thread2);
    std::thread t1(thread1);
    t1.join();
    t2.join();
    assert(a == 1 || b == 1);//true?
    return 0;
}

问题是:断言 a == 1 || b == 1 在 C++20 中总是为真吗?

我认为在 C++17 中是这样。思路是这样的:首先假设a得到0,然后证明b一定得到1。我们把它分成两部分:

1。在 SC 总顺序中 Fence X 在原子操作 B 之前

2。原子操作 B 读取原子操作 A

写入的值 (1)

C++17 在标准中有这个:

For atomic operations A and B on an atomic object M, where A modifies M and B takes its value, if there is a memory_order::seq_cst fence X such that A is sequenced before X and B follows X in S, then B observes either the effects of A or a later modification of M in its modification order.

这里确实是这样。 Op A和B在原子对象x上,其中A在x中存储1,B读取x的值;还有 seq_cst 栅栏 X; A 排在 X 之前,B 在 S 中排在 X 之后(上述);并且M没有晚于A的修改。所以B观察到A写入的值。所以b得到1.

以上推理是否正确?

但在 C++20 中,上面关于 fences 的文本被删除(由 P0668),并升级为“加强”seq_cst fences,内容如下:

An atomic operation A on some atomic object M is coherence-ordered before another atomic operation B on M if

  • A is a modification, and B reads the value stored by A, or
  • A precedes B in the modification order of M, or
  • A and B are not the same atomic read-modify-write operation, and there exists an atomic modification X of M such that A reads the value stored by X and X precedes B in the modification order of M, or
  • there exists an atomic modification X of M such that A is coherence-ordered before X and X is coherence-ordered before B.

There is a single total order S on all memory_order::seq_cst operations, including fences, that satisfies the following constraints. First, if A and B are memory_order::seq_cst operations and A strongly happens before B, then A precedes B in S. Second, for every pair of atomic operations A and B on an object M, where A is coherence-ordered before B, the following four conditions are required to be satisfied by S:

  • if A and B are both memory_order::seq_cst operations, then A precedes B in S; and
  • if A is a memory_order::seq_cst operation and B happens before a memory_order::seq_cst fence Y , then A precedes Y in S; and
  • if a memory_order::seq_cst fence X happens before A and B is a memory_order::seq_cst operation, then X precedes B in S; and
  • if a memory_order::seq_cst fence X happens before A and B happens before a memory_order::seq_- cst fence Y , then X precedes Y in S.

我看不出 a==1 || b==1 是如何保证的。这只是关于 SC 栅栏相对于其他 SC 操作或彼此一致排序的操作如何排序。

我找不到关于 SC 栅栏如何与 C++20 标准中的非 SC 原子操作交互的任何其他信息,并且没有看到值的负载来自商店并不意味着它在商店之前是连贯有序的。 (或者是吗?如果这样就可以解决问题;请参阅评论)当 A 在 M 的修改顺序中先于 B 时适用 31.4 : 3.2,但读取不是一个对象的修改顺序,是吗?

是我没有认真研究标准,还是代码在 C++20 中不再有效?如果是后者,就是回归 故意的? (在P0668中,他们声称要“加固”围栏)。

是的,我认为我们可以证明 a == 1 || b == 1 总是正确的。这里的大部分想法都是在 zwhconst 和 Peter Cordes 的评论中得出的,所以我只是想把它写成练习。

(请注意,下面的 X、Y、A、B 用作标准公理中的虚拟变量,并且可能逐行更改。它们与代码中的标签不一致。)

假设线程2中的b = x.load()产生0.

我们确实有您询问的一致性排序。具体来说,如果 b = x.load 产生 0,那么我声称线程 2 中的 x.load() 是线程 1 中的 x.store(1) 之前的一致性排序,这要归功于一致性排序定义中的第三个项目符号。设 A 为 x.load(),B 为 x.store(1),X 为初始化 x{0}(见下文的狡辩)。显然X在x的修改顺序中先于B,因为X happens-before B(同步发生在线程启动时),如果b == 0则A读取了X存储的值。

(这里可能存在差距:原子对象的初始化不是原子操作(3.18.1p3),所以按照措辞,一致性排序不适用于它。我不得不相信它是为了不过,适用于此。无论如何,我们可以通过在启动线程之前将 x.store(0, std::memory_order_relaxed); 放入 main 来避免这个问题,这仍然会解决您问题的精神。)

现在在排序 S 的定义中,像以前一样应用第二个项目符号,其中 A = x.load() 和 B = x.store(1),Y 是线程 1 中的 atomic_thread_fence。那么 A 在 B 之前是相干有序的,正如我们刚刚展示的那样; A是seq_cst; B通过排序发生在Y之前。所以因此 A = x.load() 在顺序 S.

之前 Y = fence

现在假设线程 1 中的 a = y.load() 也产生 0。

与之前类似的论证,y.load()y.store(1)之前是连贯排序的,并且它们都是seq_cst,所以y.load()y.store(1)之前S. 此外,y.store(1) 在 S 中按顺序排在 x.load() 之前,同样地,atomic_thread_fence 在 S 中排在 y.load() 之前。因此我们在 S 中有:

  • x.load 先于 fence 先于 y.load 先于 y.store 先于 x.load

这是一个循环,与S的严格排序相矛盾。