问题 C ++ 11中的双重锁定模式?


C ++ 11的新机器模型允许多处理器系统可靠地工作。重组指示。

迈耶斯和亚历山大夫斯库 指出“简单” 双重锁定模式 在C ++ 03中实现是不安全的

Singleton* Singleton::instance() {
  if (pInstance == 0) { // 1st test
    Lock lock;
    if (pInstance == 0) { // 2nd test
      pInstance = new Singleton;
    }
  }
  return pInstance;
}

他们展示了 他们的文章 无论你作为程序员做什么,在C ++ 03中编译器都有太多的自由:允许以一种你可以的方式重新排序指令  确保你最终只有一个实例 Singleton

我现在的问题是:

  • 新的C ++ 11机器模型的限制/定义现在是否限制了指令序列,上述代码总是适用于C ++ 11编译器?
  • 当使用新的库工具(而不是mock)时,现在看起来如何安全地使用这个Singleton模式的C ++ 11-Implementation Lock 这里)?

1466
2018-05-15 13:38


起源

使用单身人士 - 获得您所支付的费用。 - Puppy
另外,不要忘记C ++ 0x现在保证静态变量的初始化是线程安全的。参见§6.7/ 4:如果控制在初始化变量时同时输入声明,则并发执行应等待初始化完成;即你可以用类似的东西 static Singleton* ptr = new Singleton(); return ptr;。 - Vitus
@Vitus:的确如此。但它并没有告诉你在哪里支付锁。该公式显然需要围绕静态变量进行某种锁定,具体而言 那 锁定 双重锁定 正试图避免。 - towi


答案:


如果 pInstance 是一个常规指针,代码有一个潜在的数据竞争 - 指针上的操作(或任何内置类型,就此而言)不保证是原子的 (编辑:或订购良好)

如果 pInstance 是一个 std::atomic<Singleton*>    Lock 内部使用 std::mutex 实现同步(例如,如果 Lock 实际上是 std::lock_guard<std::mutex>),代码应该是数据竞争免费。

请注意,您需要  显式锁定和原子 pInstance 实现正确的同步。


5
2018-05-15 15:13



谢谢! atomic<Instance*>,这是一个很好的暗示。 - towi
嗯,我想知道。问题(Meyers / Alexandrescu)似乎,编译器可能会重新排序指令,所以 线程A. 该 pInstance 会得到它的价值 之前  Singleton 完全创建,停止和 线程B 看到了满满的 pInstance 并使用它。是吗? atomic<Singleton*> 真的保护我吗?我看不出来。我可能需要一个完全成熟的[Memory-Ordering] [1]。 [1] [justsoftwaresolutions.co.uk/threading/... - towi
@towi - 是的,它会的。所有操作都在 std::atomic<>默认为完全顺序一致的内存排序,必须明确请求较弱的排序。顺序一致意味着每个线程看到所有操作都以完全相同的顺序发生,就好像所有操作都是完全序列化的一样。 - JohannesD
@towi - 详细说明:这个单一的总订单是整个程序中所有正确同步的操作,而不仅仅是个人的操作 atomic 变量。特别, atomic 操作 同步 该 mutex 锁定/解锁原语,以便上述代码中的比较和锁定可能不会被重新排序。我认为。 - JohannesD
我认为原子不使用互斥锁。我相当肯定它只保证编译器将以正确的顺序输出代码并插入内存屏障以阻止乱序执行。 - Tim Seguine


由于静态变量初始化现在保证是线程安全的,因此Meyer的单例应该是线程安全的。

Singleton* Singleton::instance() {
  static Singleton _instance;
  return &_instance;
}

现在您需要解决主要问题:代码中有一个Singleton。

编辑: 根据我在下面的评论:与其他实施相比,这种实施方式存在重大缺陷。如果编译器不支持此功能会发生什么?编译器将发出线程不安全的代码,甚至不发出警告。如果编译器不支持新接口,那么带锁的其他解决方案甚至都不会编译。这可能是不依赖于此功能的一个很好的理由,即使对于单身人士以外的事情也是如此。


4
2017-09-05 13:22



确实是的。到目前为止,这可能是常识:-)此外,当委员会同意该功能时,我听说他们并不完全确定这是否会花费多少开销 - 即使不在多线程程序中使用也是如此。因此,如果偏执的人可能试图规避,我很好奇 static 单例模式的局部变量因为“它更快”,在0.1%的情况下...... - towi
@towi我认为这也许是常识,但其他答案都没有提到它,所以我做到了。在撰写时我相信GCC和Clang支持它,但MSVC不支持。至少在一个指标中,这是最好的解决方案:它具有最少的代码,并且是最容易理解的。需要注意的是,伴随此需要此功能的评论可能是明智之举。如果其他实现不支持必要的功能,则会抛出编译错误,但如果没有编译器支持,这将很乐意编译为错误的代码。 - Tim Seguine


C ++ 11不会改变双重检查锁定实现的含义。如果要进行双重检查锁定工作,则需要架设合适的内存屏障/栅栏。


1
2018-05-15 13:44



好。什么是Stdlibs解决方案?我需要 atomic_xxx_fence() 以其明确的 AQUIRE 和 发布 那里的操作,还是我使用Stdlib的更高级别的接口? - towi