如何在std :: memory_order(C ++)中了解RELAXED ORDERING

时间:2019-04-14 22:39:43

标签: c++ atomic memory-model

我已经阅读了很多文章,并观看了一些Youtube视频C ++原子和内存模型(ConCpp 17,14)。

当我阅读《并发行动》 第5.3.3节放宽订购这本书时,我仍然无法理解作者在其假设下提供的示例。

>

作者的假设

  

不仅仅是编译器可以对指令重新排序。 即使线程运行相同的代码位,由于在没有显式排序约束的情况下其他线程中的操作,它们也可能在事件顺序上存在分歧,因为不同的CPU缓存和内部缓冲区可以为同一内存保存不同的值。非常重要,我再说一遍:线程不必就事件的顺序达成共识。   不仅必须基于交织操作抛出思维模型,而且还必须根据编译器或处理器对指令进行重新排序的思想抛出思维模型。

假设我们看到的代码未重新排序

示例代码:

#include <atomic>
#include <thread>
#include <assert.h>

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed); // 1
    y.store(true,std::memory_order_relaxed); // 2
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed)); // 3
    if(x.load(std::memory_order_relaxed))      // 4
        ++z;
}

int main() {
    x=false;
    y=false;
    z=0;

    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();

    assert(z.load()!=0); // 5
}

通过此链接:https://www.developerfusion.com/article/138018/memory-ordering-for-atomic-operations-in-c0x/

enter image description here

为什么x.load(relaxed)返回false但为什么y.load(relaxed)返回true

作者的结论

  

这一次断言(5)可以触发,因为x(4)的负载可以读取为false,即使y(3)的负载读取为true并且 x(1)的存储发生在之前y(2)的存储。 x和y是不同的变量,因此不存在与每个操作所产生的值的可见性有关的排序保证。

Q。为什么x的负载可以为假?

作者得出结论,断言可以触发。因此,z可以是0。 因此,if(x.load(std::memory_order_relaxed))x.load(std::memory_order_relaxed)false

但是无论如何,while(!y.load(std::memory_order_relaxed));使y true

如果我们不重新排序(1)和(2)的代码序列,怎么可能y为真,但x仍未存储?

如何理解作者提供的数字?

基于the store of x (1) happens-before the store of y (2),如果x.store(relaxed)发生在y.store(relaxed)之前,则x应该是true。但是,为什么x仍然是falsey仍然是true

2 个答案:

答案 0 :(得分:2)

您和朋友都同意x=falsey=false。有一天,您给他寄了一封信,告诉他x=true。第二天,您给他寄了一封信,告诉他y=true。您确定以正确的顺序将信件发送给他。

有时,您的朋友收到您的来信,说y=true。现在,您的朋友对x了解多少?他可能已经收到了告诉他x=true的信。但是也许邮政系统暂时丢失了它,他明天就可以收到。因此,对于他来说,x=falsex=true都是他收到y=true字母时的有效可能性。

因此,回到硅世界。线程之间的内存没有任何保证,所有其他线程的写入都以任何特定顺序出现,因此“延迟的x”完全有可能。所有添加atomic并使用relaxed的做法都是阻止两个线程在单个变量上争分夺秒地成为未定义的行为。它对订购完全没有保证。 多数民众赞成在用于更强的顺序。

或者,以一种更为粗略的方式,看一下我的MSPaint技能:

enter image description here

在这种情况下,紫色箭头(即从第一个线程到第二个线程的“ x”流)来得太晚,而绿色箭头(y交叉)发生得很快。

答案 1 :(得分:2)

<块引用>

如果我们不将(1)和(2)的代码序列重新排序,怎么可能y是真的但x仍然没有被存储?

答案部分在第一个引用中:

<块引用>

因为不同的 CPU 缓存和内部缓冲区可以为相同的内存保存不同的值。

换句话说,其他线程可以看到过时的值。所以 x 和 y 可以被存储,但还没有传播到其他线程。并且缓存传播顺序可能与其存储顺序不同。

比如三个线程,第一个修改x和y,缓存以不同的顺序传播给不同的线程:

 x == 0          x == 0          x == 0 
 y == 0          y == 0          y == 0
+-------+       +-------+
| x = 1 | ----> | x = 1 |
+-------+       +-------+
+-------+                      +-------+
| y = 1 | -------------------> | y = 1 |
+-------+                      +-------+
  x == 1         x == 1          x == 0
  y == 1         y == 0          y == 1