【问题标题】:Do atomics in C++11 prevent compiler to re-read from shared variables?C++11 中的原子是否会阻止编译器从共享变量中重新读取?
【发布时间】:2013-06-18 20:17:34
【问题描述】:

我正在寻找 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 在“如何使用“良性”数据竞争错误编译程序”中所指出的那样(变量名称已针对上面的 sn-p 进行了相应调整):

如果编译器决定它需要在两个测试之间溢出包含 local_copy_of_shared_var 的寄存器,它可能会决定避免存储该值(毕竟它只是 shared_var 的副本),而是简单地重新读取涉及 local_copy_of_shared_var 的第二次比较的 shared_var 值。

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

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

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

【问题讨论】:

    标签: c++ c++11 atomic memory-model


    【解决方案1】:

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

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

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

    【讨论】:

    • all C++03 实现中volatile 应该防止再次读取。请注意,在volatile 的原始用例中——读取/写入内存映射寄存器——重新读取值可能比共享变量产生更糟糕的效果:读取可​​能会改变硬件状态,因此读取的不仅会价值虚假,程序对受控硬件的假设也可能是虚假的。根据标准,访问 volatile 变量是可观察到的行为,因此优化可能既不会优化它,也不会添加额外的访问。
    • @celtschk 非常正确,当我写这篇文章时,我真的在考虑对 volatile 的非易失性访问重新排序的影响。有些实现不会用 volatile 重新排序非易失性,有些会 - MSVC 特别为 volatile 提供了获取-释放语义。但是,是的,编译器当然也不能引入对 volatile 的虚假读取。
    • 我认为符合 C++03 的编译器也可以进行优化,即使某些中间代码不可见,如果 shared_var 具有自动存储持续时间并且地址 &shared_var 从未被占用并且没有引用永远绑定到shared_var
    • 很久以后回到我自己的问题,我对接受的答案中的 TLDR 行产生怀疑:“TLDR:一般来说,编译器被禁止向原子引入虚假读取,因为它们会引入数据竞赛。”。原子从不对数据竞争有贡献不是真的吗(即使使用“宽松”的内存排序,参见标准中的 1.10.5)??
    • @curiousguy 大概是因为第一段末尾的评论才写的吧?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-09
    相关资源
    最近更新 更多