3

我正在寻找第二次 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 的第二个比较。

[...] 核心问题源于编译器利用了变量值在没有显式赋值的情况下无法异步更改的假设。如果在我们的设置中语言规范不允许数据竞争,那么这种假设是完全合理的。在没有数据竞争的情况下,不可能进行这样的异步更改

现在,由于原子(使用默认的 seq_cst 内存排序)应该保证没有数据竞争,并且因为它们是编译器的“提示”,即不同线程之间存在此类变量的交互,有人可能会争辩说在前面使用原子片段会阻止编译器从shared_var插入此类“重新读取” ​​,而是将local_copy_of_shared_var视为“一次性”快照,以避免两个测试之间的不一致?

我认为我的推理有问题,因为在常识的驱动下,我不会认为仅在此处使用原子可以保证编译器将采取措施,以使local_copy_of_shared_var在两次测试之间不会得到更新。另一方面,正如 Herb 在他的演讲中所说,内存模型现在保证编译器在使用原子时不应该添加任何虚假的内存操作,这(将这种情况视为虚假读取)再次表明这个例子现在是“安全的”。我很困惑,想听听社区的意见,如果我的推理中有一些错误,我可能会得到纠正。

4

1 回答 1

3

编译器不能只是随意地进行代码转换,它们必须遵循as-if规则,该规则基本上规定生成的程序必须表现得好像它执行输入程序中编写的代码一样。是什么让您提到的优化可以接受 - 即使在老派 C++03 中也是如此 - 是编译器必须能够证明 . 的值shared_var在两个对local_copy_of_shared_var. 通常这意味着所有中间代码对编译器都是可见的,并且它不包含对shared_var.

shared_var如果是非原子类型,这种优化在 C++11 中仍然是合法的,因为shared_var在另一个线程中的任何并发修改都将是数据竞争,因此是未定义的行为。使shared_varC++11 原子化是向编译器发出的一个通知,它不能证明shared_var两个引用之间没有变化,因为它可能被另一个线程更改,并且这种特定的优化不符合as-如果规则。

TLDR:通常禁止编译器向原子引入虚假读取,因为它们会引入数据竞争。

于 2013-06-18T20:40:06.540 回答