我正在寻找Herb的第二次伟大的“原子武器”谈话,我试图围绕经历整个记忆模型/序贯一致性故事的概念。 现在有一件事情在概念层面困扰我。 从谈话的角度来看,通过使用原子,我们可以“暗示”编译器关于线程之间的交互,否则编译器将无法检测到这些交互。
所以我开始担心以下情况:
int local_copy_of_shared_var = shared_var;
if (local_copy_of_shared_var > some_threshold)
{
DoSomething();
}
... Do some work
if (local_copy_of_shared_var > some_threshold)
{
DoSomethingElse();
}
在这种情况下,正如Hans Bohem在“如何使用”良性“数据竞赛”错误编译程序中指出的那样(变量名称相应地针对上面的代码段进行了调整):
如果编译器决定它需要在两个测试之间溢出包含local_copy_of_shared_var的寄存器,它可能决定避免存储该值(毕竟它只是shared_var的副本),而是简单地重新读取shared_var的值,用于涉及local_copy_of_shared_var的第二次比较。
[...]核心问题源于编译器利用假设变量值在没有显式赋值的情况下不能异步更改的假设。如果我们的设置中的语言规范不允许数据竞争,则这种假设是完全合法的。在没有数据竞争的情况下,不可能进行这种异步更改
现在,因为atomics(使用默认的seq_cst内存排序)应该保证没有数据竞争,并且因为它们是编译器的“提示”,即在不同线程之间存在这些变量的交互,可能有人认为使用原子在前面的代码片段中会阻止编译器从 shared_var 插入这样的“重新读取”,而是将 local_copy_of_shared_var 视为“一次性”快照,避免两者之间的不一致测试
我认为我的推理有些不对劲,因为在常识的推动下我不会认为只要在这里使用原子,我就可以保证编译器会采取措施以便 local_copy_of_shared_var 在两个测试之间没有更新。 另一方面,正如Herb在他的演讲中所说的那样,内存模型现在保证编译器在使用原子时不应该添加虚假的内存操作,这样做(将这种情况视为虚假读取)会再次表明此示例现在是“安全”。 我很困惑,想听听社区的意见,如果我的推理中有一些错误,可能会得到纠正。
答案 0 :(得分:3)
编译器无法直接进行代码转换,他们必须遵循as-if rule,这基本上表明生成的程序必须表现,好像它执行代码如输入程序中所写。即使在旧式C ++ 03中,您所引用的优化的原因是编译器必须能够证明shared_var
的值在local_copy_of_shared_var
的两个引用之间不会发生变化。通常,这意味着编译器可以看到所有插入代码,并且它不包含shared_var
的赋值。
如果shared_var
是非原子类型,则此优化在C ++ 11中仍然是合法的,因为在另一个线程中shared_var
的任何并发修改都将是数据争用,因此是未定义的行为。使shared_var
成为C ++ 11 atomic是一个通知编译器它不能证明shared_var
在两个引用之间没有变化,因为它可能是由另一个线程更改,并且此特定优化不符合 as-if 规则。
TLDR:一般情况下,编译器禁止将虚假读取引入原子,因为它们会引入数据竞争。