std::memory_order_seq_cst 的工作原理

How std::memory_order_seq_cst works

我以 std::memory_order_seq_cst 为例: http://en.cppreference.com/w/cpp/atomic/memory_order

#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
}

Acquire/Release versus Sequentially Consistent memory order的问题中也提到了这个例子。

我的问题是线程 c 和线程 d 怎么可能看到不同的东西?如果可能的话,为什么下面这个简单的例子总是屈服于 z=3?例如,线程 b 可以说 "okay I see 0 even though thread a is already done so z becomes 0+1 again"

#include <atomic>
#include <iostream>

std::atomic<int> z = {0};

void increment()
{
    z.fetch_add(1, std::memory_order_relaxed);
}
int main()
{
    std::thread a(increment);
    std::thread b(increment);
    std::thread c(increment);
    a.join(); b.join(); c.join();
    std::cout << z.load() << '\n';
}

因为read-modify-write操作有特殊的保证。

根据标准[atomics.order] paragraph 11:

Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.

因此,通过在您的评论中看到不同的内容,您的意思是 线程 C 看到 x==1,y==0 并且线程 D 看到 x==0 和 y==1.顺序一致性可能吗?

让我们假设这个总顺序(修改是这个符号化记忆状态之间的转换):

{x==0,y==0} : S0
{x==1,y==0} : S1
{x==1,y==1} : S2

当我们说 "see" 时,我们的意思是线程可能执行负载。两个加载不能在一个线程中同时执行。那么线程 C 怎么可能看到 x==1 then 看到 y==0 而线程 D 看到 x==0 then 看到 y ==1?线程 C 在内存处于状态 S1 时执行这两个加载,线程 D 在状态 S0 看到 x,然后在状态 S2 看到 y

在您的示例代码中,线程 C 加载 x 然后加载 y,线程 D 重复加载 y 直到它为真,然后加载 x。所以在y==1之后,保证在这个总序中x==1

正如 Minee 在其评论中所说,如果使用顺序一致性内存顺序代替顺序一致性内存顺序,则不会有任何期望 acquire/release 内存顺序:acquire/release 语义并不意味着任何总顺序,此外在 x 的商店和 y 的商店之间没有 之前发生的关系。所以断言 z.load()!=0 可能会触发。

My question is how it is possible that thread c and thread d see different things?

理论上是允许的,如果您有多个原子变量并且某些操作没有 memory_order_seq_cst 排序,那么在实践中它可能会发生。

所以在您的代码中不可能在所有操作上使用 memory_order_seq_cst (仅在某些操作上使用它是危险的,因为它会导致细微的错误) .

For instance, thread b could say "okay I see 0 even though thread a is already done so z becomes 0+1 again"

没有

但在任何情况下,单个原子变量上允许的内容与内存排序无关,这会影响其余内存的可见性,并且不会影响其上的对象你正在经营.

如果您只有一个原子变量而没有其他共享状态,则可见性无关紧要,因为没有任何内容可以显示。

[标准说明注意事项:

该标准暗示至少在理论上,该断言并非在所有情况下都适用于宽松的操作。但是线程上的标准是疯狂的:除非我的断言是真的,否则它定义不正确。

无论如何,该标准表示在实践中,实现应避免允许在我的断言为假的情况下执行。在实践中,它们不会随时随地发生。]