原子载荷可以在C++内存模型中合并吗?

Jul*_*ina 19 c++ memory-model compiler-optimization language-lawyer stdatomic

考虑下面的C++ 11片段.对于GCC和clang,这会编译为两个(顺序一致的)foo.C++内存模型是否允许编译器将这两个加载合并到一个加载中并对x和y使用相同的值?

我认为它不能合并这些负载,因为这意味着轮询原子不再起作用,但我找不到内存模型文档中的相关部分.

#include <atomic>
#include <cstdio>

std::atomic<int> foo;

int main(int argc, char **argv)
{
    int x = foo;
    int y = foo;

    printf("%d %d\n", x, y);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Fil*_*efp 18

是的,因为我们无法观察到差异!

允许实现将您的代码段转换为以下内容(伪实现).

int __loaded_foo = foo;

int x = __loaded_foo;
int y = __loaded_foo;
Run Code Online (Sandbox Code Playgroud)

原因是,鉴于顺序一致性的保证,您无法观察上述和两个单独的foo之间的差异.

注意:不只是编译器可以进行这样的优化,处理器可以简单地推断,没有办法可以观察到差异并加载foo一次的值- 即使编译器可能已经要求它执行它两次.





说明

给定一个以增量方式继续更新foo的线程,保证的是,与内容相比,y它将具有相同更高的写入值x.

// thread 1 - The Writer
while (true) {
  foo += 1;
}
Run Code Online (Sandbox Code Playgroud)
// thread 2 - The Reader
while (true) {
  int x = foo;
  int y = foo;

  assert (y >= x); // will never fire, unless UB (foo has reached max value)
}                  
Run Code Online (Sandbox Code Playgroud)

想象一下,写入线程由于某种原因暂停其在每次迭代时的执行(因为上下文切换或其他实现定义的原因); 没有办法可以证明这是导致两者xy具有相同值的原因,或者是因为"合并优化".


换句话说,鉴于本节中的代码,我们必须得到潜在的结果:

  1. 在两个读取()之间没有向foo写入新值x == y.
  2. 在两个读取()之间将新值写入foox < y.

由于这两者中的任何一个都可以发生,因此实现可以自由地缩小范围以简单地始终执行其中一个; 我们无法观察到这种差异.





标准说什么?

只要我们无法观察到我们表达的行为与执行期间的行为之间的任何差异,实现就可以进行任何需要的更改.

这包含在[intro.execution]p1:

本国际标准中的语义描述定义了参数化的非确定性抽象机器.本国际标准对符合实施的结构没有要求.特别是,它们不需要复制或模拟抽象机器的结构.相反,需要符合实现来模拟(仅)抽象机器的可观察行为,如下所述.

另一节让它更加清晰[intro.execution]p5:

执行格式良好的程序的一致实现应该产生 与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为.

进一步阅读:





在循环中轮询怎么样?

// initial state
std::atomic<int> foo = 0;
Run Code Online (Sandbox Code Playgroud)
// thread 1
while (true) {
  if (foo)
    break;
}
Run Code Online (Sandbox Code Playgroud)
// thread 2
foo = 1
Run Code Online (Sandbox Code Playgroud)

问题:鉴于前面部分的推理,一个实现只能foo线程1中读取一次,然后即使线程2写入也不会突破循环foo

答案; 没有.

在顺序一致的环境,我们保证一个写foo的线程2将成为可见的线程1 ; 这意味着当发生写入时,线程1必须遵守这种状态变化.

注意:实现可以将两个读取转换为单个读取,因为我们无法观察到差异(一个栅栏与两个栅格一样有效),但它不能完全忽略自身存在的读取.

注意:本节内容由以下内容保证[atomics.order]p3-4.





如果我真的想阻止这种形式的"优化"怎么办?

如果您想强制实现在您编写它的每个点上实际读取某个变量的值,您应该考虑使用volatile(请注意,这绝不会增强线程安全性).