Sequentially consistent ordering in C++ 定义了一个全序(total order)。

huangapple go评论131阅读模式
英文:

How sequentially consistent ordering in c++ define a total order?

问题

I'll provide a translation of the text you provided:

我正在阅读来自https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering 的以下示例。我很难理解以下情况下 assert(z.load() != 0); 会失败:

  • 何时会失败
  • 为什么使用 memory_order_seq_cst 而不是 memory_order_ack_rel 会使 z 永远不为 0。
#include <thread>
#include <atomic>
#include <cassert>
 
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
 
void write_x()
{
    x.store(true, std::memory_order_seq_cst);
}
 
void write_y()
{
    y.store(true, std::memory_order_seq_cst);
}
 
void read_x_then_y()
{
    while (!x.load(std::memory_order_seq_cst))
        ;
    if (y.load(std::memory_order_seq_cst)) {
        ++z;
    }
}
 
void read_y_then_x()
{
    while (!y.load(std::memory_order_seq_cst))
        ;
    if (x.load(std::memory_order_seq_cst)) {
        ++z;
    }
}
 
int main()
{
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join(); b.join(); c.join(); d.join();
    assert(z.load() != 0);  // 永远不会发生
}

据我了解,无论是 read_x_then_y 还是 read_y_then_x,它们都可以观察到以下状态之一:

  • x = truey = false
  • x = falsey = true
  • x = truey = true
  • x = falsey = false

前两种情况会使 z = 1(最终为2),第三种情况会使 z = 2,而最后一种情况会使 read_x_then_yread_y_then_x 等待,直到 xy 中的一个变为 true。然而,根据 cppreference 上的说明:

此示例演示了需要顺序排序的情况。任何其他排序都可能触发断言,因为线程 c 和 d 可能以相反的顺序观察到对原子变量 x 和 y 的更改。

我不明白这是如何可能的。为什么对 xy 的更改会以相反的顺序发生?

此外,我想知道使用 memory_order_seq_cst 如何解决这个问题。它是否强制要求在 read_x_then_y 中执行的 x.load 必须在 y.load 之前执行?

请注意,我已经省略了代码部分,只提供了翻译的内容。如果您需要进一步的解释或回答,请告诉我。

英文:

I am reading the following example from https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering. And I have a hard time understanding

  • under what situation assert(z.load() != 0); will fail.
  • why does using memory_order_seq_cst over memory_order_ack_rel make z never be 0.
#include <thread>
#include <atomic>
#include <cassert>
 
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
 
void write_x()
{
    x.store(true, std::memory_order_seq_cst);
}
 
void write_y()
{
    y.store(true, std::memory_order_seq_cst);
}
 
void read_x_then_y()
{
    while (!x.load(std::memory_order_seq_cst))
        ;
    if (y.load(std::memory_order_seq_cst)) {
        ++z;
    }
}
 
void read_y_then_x()
{
    while (!y.load(std::memory_order_seq_cst))
        ;
    if (x.load(std::memory_order_seq_cst)) {
        ++z;
    }
}
 
int main()
{
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join(); b.join(); c.join(); d.join();
    assert(z.load() != 0);  // will never happen
}

As far as I under, for either read_x_then_y or read_y_then_x, they could observe a state among:

  • x = true and y = false
  • x = false and y = true
  • x = true and y = true
  • x = false and y = false

The first 2 cases make z = 1 (eventually 2), the third case makes z = 2, and the last case makes read_x_then_y and read_y_then_x wait until one of `x' and 'y' become true. However, according to the cppreference
> This example demonstrates a situation where sequential ordering is necessary. Any other ordering may trigger the assert because it would be possible for the threads c and d to observe changes to the atomics x and y in opposite order.

I don't understand how is that possible. How would the changes to x and y be in the opposite order?

In addition, I am wondering how would the use of memory_order_seq_cst solves the problem. Is it forcing the x.load in read_x_then_y must be executed before y.load?

答案1

得分: 2

我建议阅读我的《内存顺序介绍》,其中解释了大部分内容。

如何使 x 和 y 的更改顺序相反?

因为默认情况下(在没有 seq_cst 的情况下),线程只同意每个单独的原子变量上的操作顺序。它们可以在这些操作如何交错上产生分歧。

因此,如果移除 seq_cst,所有线程只会同意 xy 最初都是 false 并且被更改为 true,但它们可以在哪个先更改的问题上产生分歧。

此外,我想知道使用 memory_order_seq_cst 如何解决这个问题。

因为对于 seq_cst 操作,所有线程都同意它们如何交错。换句话说,存在一种所有线程都同意的单一总顺序,其中发生所有 seq_cst 操作。

因此,如果线程 read_x_then_y() 看到 x == true && y == false 并执行 z++,那么我们已经确定了 x = true 发生在 y = true 之前,因此线程 read_y_then_x() 必须同意这一观察,不能看到 x == false && y == true

英文:

I suggest reading my introduction to memory orders, which explains most of this.

> How would the changes to x and y be in the opposite order?

Because by default (in absence of seq_cst) threads only agree on the order of operations on each individual atomic variable. They can disagree on how those operations are interleaved.

So if you remove seq_cst, all threads will merely agree that x and y start as false and are changed to true, but they can disagree which of them is changed first.

> In addition, I am wondering how would the use of memory_order_seq_cst solves the problem.

Because for seq_cst operations, all threads agree on how they are interleaved. In other words, there's a single total order (that all threads agree on) in which all seq_cst operations happen.

So if thread read_x_then_y() sees x == true && y == false and does z++, then we've estabilished that x = true "happens before" y = true, so the thread read_y_then_x() has to agree with this observation, and can't see x == false && y == true.

答案2

得分: 0

以下是已翻译的内容:

对于read_x_then_y

void read_x_then_y()
{
    while (!x.load(std::memory_order_seq_cst))
        ;
    if (y.load(std::memory_order_seq_cst)) {
        ++z;
    }
}

第一个循环等待x加载为true。然后,如果y加载为true,则执行++z。此时,y可以是truefalse。两种执行都可能。

同样地,对于read_y_then_x,两种执行都可能。

是什么阻止了以下情况?

  • read_x_then_yx加载为true,但将y加载为false
  • read_y_then_xy加载为true,但将x加载为false

在这种情况下,会触发断言。

答案是这是顺序一致性排序提供的保证。所有这些原子操作共享一个单一的总修改顺序。要么对x的存储发生在首位,要么对y的存储发生在首位,按照这个顺序。所有加载将在该顺序上达成一致。

发布存储和获取加载提供了更少的保证:同一线程中的发布存储之前的所有存储对其他线程中的获取加载可见。因此,在加载x之后,由write_x存储的任何变量都可以由read_x_then_y加载。当然,没有这样的存储。

英文:

For read_x_then_y:

void read_x_then_y()
{
    while (!x.load(std::memory_order_seq_cst))
        ;
    if (y.load(std::memory_order_seq_cst)) {
        ++z;
    }
}

The first loop waits until x is loaded as true. Then ++z is executed if y is loaded as true. At this point, y could be either true or false. Both executions are possible.

Likewise, for read_y_then_x, both executions are possible.

What prevents the following?

  • read_x_then_y loads x as true but y as false
  • read_y_then_x loads y as true but x as false

In this case, the assert would trigger.

The answer is that this is the guarantee sequentially consistent ordering provides. All such atomic operations share a single total modification order. Either the store to x happens first, or the store to y happens first, in this order. All loads will agree on that order.

Release stores and acquire loads provide much less: any stores before the release stores in the same thread are visible to acquire loads in other threads. So all variables stored to by write_x before the store, would be loadable by read_x_then_y after x is loaded. Of course, there are no such stores.

huangapple
  • 本文由 发表于 2023年6月6日 12:24:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76411436.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定