用放松的顺序读取共享变量:理论上是否可能?在C ++中有可能吗?

时间:2014-05-01 09:37:14

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

考虑以下伪代码:

expected = null;
if (variable == expected)
{
    atomic_compare_exchange_strong(
        &variable, expected, desired(), memory_order_acq_rel, memory_order_acq);
}
return variable;

执行variable == expected检查时,观察 no “获取”语义。

在我看来,desired至少会被调用一次,每个线程最多一次。 此外,如果desired永远不会返回null那么此代码永远不会返回null

现在,我有三个问题:

  1. 以上一定是真的吗?也就是说,即使在每次阅读时没有围栏,我们是否真的可以对共享变量进行有序读取?

  2. 是否可以在C ++中实现它?如果是这样,怎么样?如果没有,为什么?
    (希望有一个基本原理,而不仅仅是“因为标准这么说”。)

  3. 如果(2)的答案是肯定的,那么是否也可以在C ++ 中实现这一点而不需要要求variable == expected执行 atomic 阅读variable

  4. 基本上,我的目标是了解是否有可能以一种代码已经具有与非共享变量相同的方式执行共享变量的延迟初始化每个线程至少执行一次?

    (这有点像“语言 - 律师”问题。所以这意味着问题不在于这是一个好的还是有用的想法,而是在技术上是否可以正确地做到这一点。)

2 个答案:

答案 0 :(得分:4)

关于是否可以在C ++中执行共享变量的延迟初始化的问题,其性能(几乎)与非共享变量的性能(几乎)相同:

答案是,它取决于硬件架构,以及编译器和运行时环境的实现。至少,在某些环境中是可能的。尤其是在带有GCC和Clang的x86上。

在x86上,可以在没有内存防护的情况下实现原子读取。基本上,原子读取与非原子读取相同。看看下面的编译单元:

std::atomic<int> global_value;
int load_global_value() { return global_value.load(std::memory_order_seq_cst); }

虽然我使用顺序一致性(默认值)的原子操作,但生成的代码没有什么特别之处。 GCC和Clang生成的汇编程序代码如下:

load_global_value():
    movl global_value(%rip), %eax
    retq

我说几乎相同,因为还有其他可能影响性能的原因。例如:

  • 虽然没有围栏,但原子操作仍会阻止某些编译器优化,例如:重新排序说明和消除商店和货物
  • 如果至少有一个线程写入同一缓存线上的不同内存位置,则会对性能产生巨大影响(称为 false sharing

话虽如此,实现延迟初始化的推荐方法是使用std::call_once。这应该为所有编译器,环境和目标架构提供最佳结果。

std::once_flag _init;
std::unique_ptr<gadget> _gadget;

auto get_gadget() -> gadget&
{
    std::call_once(_init, [this] { _gadget.reset(new gadget{...}); });
    return *_gadget;
}

答案 1 :(得分:3)

这是未定义的行为。您正在修改variable,at 至少在某个线程中,这意味着所有访问 变量必须受到保护。特别是,当你做的时候 在一个线程中执行atomic_compare_exchange_strong, 没有什么可以保证另一个线程可能会看到 variable的新值在看到可能的写入之前 发生在desired()。 (atomic_compare_exchange_strong 只保证执行它的线程中的任何排序。)