是的,因为我们无法观察到差异!
允许实现将您的代码段转换为以下代码(伪实现)。
int __loaded_foo = foo;
int x = __loaded_foo;
int y = __loaded_foo;
原因是您无法观察到上述内容之间的差异,并且考虑到顺序一致性的保证,两个单独的foo负载。
注意:不仅仅是编译器可以进行这种优化,处理器可以简单地推断出你无法观察到差异并加载foo
一次的值——即使编译器可能已经要求它这样做两次。
解释
给定一个以增量方式不断更新foo的线程y
,与x
.
// thread 1 - The Writer
while (true) {
foo += 1;
}
// 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)
}
想象一下,由于某种原因,编写线程在每次迭代时都会暂停其执行(因为上下文切换或其他实现定义的原因);您无法证明这是导致两者x
具有y
相同值的原因,或者是因为“合并优化”。
换句话说,我们必须给定本节中的代码的潜在结果:
- 在两次读取 ( )之间没有向foo写入新值。
x == y
- 在两次读取 ( )之间将一个新值写入foo 。
x < y
由于这两种情况中的任何一种都可能发生,因此实现可以自由地缩小范围以简单地始终执行其中一个;我们无法观察到差异。
标准是怎么说的?
只要我们无法观察到我们表达的行为与执行期间的行为之间的任何差异,实现就可以进行任何它想要的更改。
这包括在[intro.execution]p1
:
本国际标准中的语义描述定义了一个参数化的非确定性抽象机。本国际标准对一致性实现的结构没有要求。特别是,它们不需要复制或模仿抽象机器的结构。相反,需要符合要求的实现来模拟(仅)抽象机的可观察行为,如下所述。
另一部分使其更加清晰[intro.execution]p5
:
执行格式良好的程序的一致实现应产生
与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。
延伸阅读:
循环轮询呢?
// initial state
std::atomic<int> foo = 0;
// thread 1
while (true) {
if (foo)
break;
}
// thread 2
foo = 1
问题:根据前面几节的推理,一个实现是否可以简单地在线程 1foo
中读取一次,然后即使线程 2写入也永远不会跳出循环?foo
答案; 不。
在顺序一致的环境中,我们保证在线程 2中写入foo将在线程 1中可见;这意味着当写入发生时,线程 1必须观察这种状态变化。
注意:一个实现可以将两个读取变成一个读取,因为我们无法观察到差异(一个栅栏与两个栅栏一样有效),但它不能完全忽略本身存在的读取。
注:本节内容由[atomics.order]p3-4
.
如果我真的想阻止这种形式的“优化”怎么办?
如果您想强制实现在您编写它的每个点实际读取某个变量的值,您应该研究使用volatile
(请注意,这绝不会增强线程安全性)。
但在实践中,编译器不会优化 atomics,标准组建议不要使用volatile atomic
这种原因,直到尘埃落定这个问题。看