当前位置 : 主页 > 编程语言 > c++ >

c – 了解内存序列和std :: memory_order_relaxed

来源:互联网 收集:自由互联 发布时间:2021-06-23
我正在研究C内存序列,但它非常令人困惑. 例如: void sumUp(std::atomicint sum, std::vectorint val){ int tmpSum = 0; for(auto i = 0; i 100; ++i) tmpSum += val[i]; sum.fetch_add(tmpSum, std::memory_order_relaxed);} 我不明白
我正在研究C内存序列,但它非常令人困惑.

例如:

void sumUp(std::atomic<int>& sum, std::vector<int>& val)
{
   int tmpSum = 0;
   for(auto i = 0; i < 100; ++i) tmpSum += val[i];

   sum.fetch_add(tmpSum, std::memory_order_relaxed);
}

我不明白sum.fetch_add()在tmpSum = val [i]之后运行.
由于它不是有序的,sum.fetch_add()可以在tmpSum = val [i]之前运行吗?

总和0可能吗?

非常感谢.

memory_order在单个线程的上下文中没有可观察到的效果:

让我们看看(x,a和b最初为0):

auto t1(std::atomic<int>& x, int& a, int& b)
{
    a = 3;                                       // 1
    b = 5;                                       // 2
    x.store(a, std::memory_order_relaxed);       // 3
}

因为(1)和(2)不依赖于另一个,所以编译器可以重新排序它们.例如.可以做(1) – > (2)或(2) – > (1)

因为(3)依赖于(1)((1)写入a和(3)从a读取)编译器在(1)之前不能做(3).这与(3)中指定的内存顺序无关

因为(3)不依赖于(2),通常在单线程模型中,编译器可以在(2)之前做(3).

但由于x是原子的,考虑另一个线程这样做(x,a和b是对提供给t1的相同参数的引用,并且最初都是0):

auto t2(std::atomic<int>& x, int& a, int& b)
{
    while(x.load(std::memory_order_relaxed) == 3)  // 4
        assert(b == 5);                            // 5
}

该线程等待直到x为3,然后断言b为5.现在您可以看到顺序单线程世界(2)和(3)如何在没有任何可观察行为的情况下重新排序,但在多线程模型中,顺序(2)和(3)可能会对程序的行为产生影响.

这就是memory_order的作用:它指定在原子之前或之后可以对单个线程产生任何影响的操作是否可以重新排序.原因是它们可能对多线程程序产生影响.编译器不能知道这个,只有程序员,因此额外的memory_order参数.

对于memory_order_relaxed,断言可能会失败,因为(2)可能发生在(3)之后,但是对于memory_order_seq_cst(默认),断言将永远不会失败,因为(2)发生在(3)之前.

回到你的例子,无论你指定什么memory_order,都保证tmpSum = val [i];会在sum.fetch_add(tmpSum,std :: memory_order_relaxed)之前发生;因为第二个取决于第一个. memory_order会影响可能重新排序不影响原子操作的指令.例如.如果你有一个无关的int = 24.

顺便说一句,官方术语是“先前排序”和“后排序”

在现实世界中,硬件使事情变得复杂一些.操作可以在当前线程中以一个顺序出现,但另一个线程可以以另一个顺序看到它们,因此更严格的memory_orders必须采用额外的措施来确保跨线程的顺序一致.

严格来说,在此示例中,如果使用memory_order_relaxed,我们将具有未定义的行为,因为对b的访问不会跨线程同步.

网友评论