c++中的顺序一致性排序如何定义总顺序?

fivyi3re  于 2023-06-07  发布在  其他
关注(0)|答案(2)|浏览(169)

我正在从www.example.com上阅读下面的例子https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering。我很难理解

  • 在什么情况下assert(z.load() != 0);会失败。
  • 为什么在memory_order_ack_rel上使用memory_order_seq_cst会使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);  // will never happen
}

就我所知,对于read_x_then_yread_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等待,直到“x”和“y”之一变为真。然而,根据cppreference
这个例子演示了一种需要顺序排序的情况。任何其他排序都可以触发Assert,因为线程c和d可能以相反的顺序观察原子x和y的变化。
我不明白这怎么可能。x和y的变化如何以相反的顺序进行?
另外,我想知道如何使用memory_order_seq_cst解决这个问题。是否强制read_x_then_y中的x.load必须在y.load之前执行?

ddrv8njm

ddrv8njm1#

我建议阅读我的introduction to memory orders,它解释了大部分内容。
x和y的变化如何以相反的顺序进行?
因为默认情况下(在没有seq_cst的情况下)线程只同意对每个原子变量的操作顺序。他们可能会对这些操作的交叉方式产生分歧。
因此,如果删除seq_cst,所有线程只会同意xyfalse开始并更改为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

s3fp2yjn

s3fp2yjn2#

对于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

在这种情况下,Assert将触发。
答案是,这是顺序一致排序提供的保证。所有这样的原子操作共享单个总修改顺序。按照这个顺序,要么先存储到x,要么先存储到y。所有货物将同意该订单。
释放存储和获取加载提供的功能要少得多:相同线程中的释放存储之前的任何存储对于获取其它线程中的加载是可见的。因此,在存储之前由write_x存储的所有变量都可以在加载x之后由read_x_then_y加载。当然,没有这样的商店。

相关问题