【问题标题】:C++ memory model: do seq_cst loads synchronize with seq_cst stores?C++ 内存模型:seq_cst 加载是否与 seq_cst 存储同步?
【发布时间】:2018-05-11 06:30:23
【问题描述】:

在 C++ 内存模型中,所有顺序一致的操作的所有加载和存储都有一个总顺序。我想知道这如何与具有其他内存顺序的操作交互,这些内存顺序在顺序一致的加载之前/之后进行排序。

例如,考虑两个线程:

std::atomic<int> a(0);
std::atomic<int> b(0);
std::atomic<int> c(0);

//////////////
// Thread T1
//////////////

// Signal that we've started running.
a.store(1, std::memory_order_relaxed);

// If T2's store to b occurs before our load below in the total
// order on sequentially consistent operations, set flag c.
if (b.load(std::memory_order_seq_cst) == 1) {
  c.store(1, std::memory_order_relaxed)
}


//////////////
// Thread T2
//////////////

// Blindly write to b.
b.store(1, std::memory_order_seq_cst)

// Has T1 set c? If so, then we know our store to b occurred before T1's load
// in the total order on sequentially consistent operations.
if (c.load(1, std::memory_order_relaxed)) {
  // But is this guaranteed to be visible yet?
  assert(a.load(1, std::memory_order_relaxed) == 1);
}

是否保证T2中的断言不会触发?

我在这里寻找标准的详细引用。特别是我认为这需要显示从 T1 中的 b 的负载 存储同步到 T2 中的 b 以便确定存储到 a 线程间发生在从 a 加载之前,但据我所知,标准说 memory_order_seq_cst 存储与加载同步,但不是相反。

【问题讨论】:

    标签: c++ language-lawyer atomic memory-model stdatomic


    【解决方案1】:

    seq_cst 加载是否与 seq_cst 存储同步?

    如果所有必要的要求都得到满足,他们就会这样做;在您的示例代码中,assert 可以触发

    §29.3.3
    所有 memory_order_seq_cst 操作都应有一个总订单 S

    此总顺序适用于 seq_cst 操作本身。单独来看,store(seq_cst) 具有释放语义,而 load(seq_cst) 具有获取语义。

    §29.3.1-2 [atomics.order]
    memory_order_release、memory_order_acq_rel 和 memory_order_seq_cst:
    存储操作对受影响的内存位置执行释放操作。
    .....
    §29.3.1-4 [atomics.order]
    memory_order_acquire、memory_order_acq_rel 和 memory_order_seq_cst:
    加载操作对受影响的内存位置执行获取操作。

    因此,具有非seq_cst 排序的原子操作(或非原子操作)根据获取/释放排序规则相对于seq_cst 操作进行排序:

    • store(seq_cst) 操作不能与任何在它之前排序的内存操作重新排序(即在程序顺序中更早)..
    • load(seq_cst) 操作不能与任何在它之后排序的内存操作重新排序。

    在您的示例中,尽管T1 中的c.store(relaxed)b.load(seq_cst) 之后排序(线程间)(load 是一个获取操作), T2 中的 c.load(relaxed) 相对于 b.store(seq_cst) 是无序的(这是一个释放操作,但不会阻止重新排序)。

    您还可以查看a 上的操作。由于它们没有针对任何东西排序,a.load(relaxed) 可以返回0,从而导致assert 被触发。

    【讨论】:

    • "acquire 操作和 release 操作".. cppreference 有时会令人困惑;这只适用于读-修改-写操作。
    • @PeterCordes 您的评论让我(再次)意识到使用特定术语很容易混淆事物。我更新了分析器以尝试使用“标准”。
    • 看起来不错。在这种情况下,我认为说“标准语”不会丢失任何东西,但另一种方法可能是说“获取负载设置了单向障碍”。然后可以说“不像 2-way std::atomic fences”并链接 Jeff Preshing 的优秀障碍与负载/存储文章。
    • @PeterCordes 我完全同意.. 使用 1-way barrier 方法来描述事物的工作方式是绝对安全的。它也是(至少对我而言)可视化事物的最简单方法。
    • 但是你所说的“2-way屏障”是什么意思?我对“双向”的解释是,相同的规则将适用于两个方向,例如,如果商店不能单向穿过障碍(即,旧商店在障碍之前迁移),那么他们就不能穿过其他方式。因此,非对称障碍(例如,允许商店以一种方式迁移,但不能以另一种方式迁移)不能称为 2 向障碍。但这并不意味着它是一个单向障碍:它是一个不对称的障碍,限制双向运动,但以相反的方式。例如,Power lwsync 就是这样一个不对称的屏障。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-06
    • 2020-10-10
    • 1970-01-01
    • 2012-01-04
    • 2010-10-12
    • 2012-11-25
    相关资源
    最近更新 更多