std :: memory_order_seq_cst如何工作

时间:2018-02-24 13:48:52

标签: c++ c++11 memory-barriers

我从以下网站获取了关于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可以说“好吧我看到0即使线程a已经完成所以z再次变为0 + 1”

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

3 个答案:

答案 0 :(得分:3)

因为读 - 修改 - 写操作有特殊保证。

根据标准[atomics.order] paragraph 11

  

原子读 - 修改 - 写操作应始终读取与读 - 修改 - 写操作相关的写操作之前写入的最后一个值(按修改顺序)。

答案 1 :(得分:1)

因此,通过在评论中看到不同的东西,你的意思是线程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

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

在您的示例代码中,发生的是Thread C加载x然后加载y,并且Thread D重复加载y,直到它为true然后加载x。因此,在y == 1之后,在整个订单中保证x==1

正如Minee在其评论中所说,如果代替顺序一致性内存顺序使用获取/释放内存顺序,则无法预期:获取/释放语义并不意味着任何总排序,而且没有发生之前商店与x和商店之间的关系y。所以断言z.load()!=0可以解雇。

答案 2 :(得分:0)

  

我的问题是线程c和线程d怎么可能看到   不同的东西?

理论上是允许的,实际上,如果您有多个原子变量并且某些操作没有memory_order_seq_cst顺序,则有可能发生。

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

  

例如,线程b可能会说“好吧,即使线程a是   已经完成,因此z再次变为0 + 1”。

否。

但是无论如何,单个原子变量上允许的操作与内存排序无关,这会影响其余内存的可见性,并且不会影响正在操作的对象< / em>。

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

[关于标准说明的注意事项:

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

无论如何,该标准指出,在实践中,实现应避免在我的断言为假的情况下允许执行。而且在实践中它们不会在任何地方发生。]