【问题标题】:Double-checked locking in C++11? [duplicate]C ++ 11中的双重检查锁定? [复制]
【发布时间】:2013-05-07 06:22:16
【问题描述】:

这是来自http://www.ibm.com/developerworks/java/library/j-dcl/index.html的Java示例问题

public static Singleton getInstance()
{
  if (instance == null) //#4
  {
    synchronized(Singleton.class) {  //#1
      if (instance == null)          //#2
        instance = new Singleton();  //#3
    }
  }
  return instance;
}

这似乎不安全,因为#3 可以在构造函数执行之前将实例设置为不为空,因此当另一个线程检查 #4 上的实例时,它不会为空并返回一个未正确构造的实例。

显然使用函数变量不会有帮助,因为它可能会被优化或者只是以一种在我们不希望它时也将值设置为实例的方式执行。

我在想最简单的方法不是拥有一个函数new Singleton();,所以它在分配给实例之前就完成了。现在的问题是我如何告诉 C++ 一个函数不应该是内联的?我认为 Singleton* make_singleton() volatile 应该这样做,但我很肯定我错了。

【问题讨论】:

  • 我有点困惑。您问题中的文章和代码是关于 Java 的,然后您在谈论 C++11。您是否想弄清楚如何在 C++ 中实现这个习语?如果是这样,您链接的文章已经讨论了为什么 Java 版本不起作用。你的 C++ 函数是什么样的?
  • 恐怕你会一直吸引那些缺乏阅读理解力并认为你在谈论 Java 的人投反对票。
  • 不要使用单例。 :)
  • @GManNickG: :D 我从不这样做。 new 在使用线程时可能会遇到问题,这让我很烦恼

标签: c++ c++11 concurrency race-condition


【解决方案1】:

我将暂时忽略单例位,并假设您需要它来进行延迟初始化,而不是单例等愚蠢的事情。

我建议忘记双重检查锁定。 C++ 以std::call_once 的形式为这种情况提供了一个非常有用的工具,因此请使用它。

template <typename T>
struct lazy {
public:
    // needs constraining to prevent from doing copies
    // see: http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html
    template <typename Fun>
    explicit lazy(Fun&& fun) : fun(std::forward<Fun>(fun)) {}

    T& get() const {
         std::call_once(flag, [this] { ptr.reset(fun()); });
         return *ptr;
    }
    // more stuff like op* and op->, implemented in terms of get()

private:
    std::once_flag flag;
    std::unique_ptr<T> ptr;
    std::function<T*()> fun;
};

// --- usage ---

lazy<foo> x([] { return new foo; });

【讨论】:

  • 我建议使用call_once 而不是std::call_once。在这里没有什么区别,但通常应该更喜欢调用不合格的函数以允许 ADL 生效。由于flagstd::once_flag 类型,命名空间std 将是一个关联的命名空间,因此std:: 是不必要的。
  • 呃。不。一个人通常应该更喜欢任何能达到自己目标的东西。我想打电话给std::call_once,仅此而已,不是其他任何事情,让std:: 绝对至关重要以确保这一点。
  • 我同意在这种情况下有效,而且它主要是一种风格。但是,我看到很多人打电话给std::swap,这通常是错误的(尤其是在模板代码中),因为它会抑制 ADL。这就是为什么我建议总是调用不合格的函数,除非有特定的需要来精确控制调用哪个函数并且 ADL 会做错事。因为这里 ADL 会选择std::call_once,所以我的建议是不要抑制 ADL。
  • @AdamH.Peterson 不合格调用swap 的情况与标准库中的其他函数完全不同。 swap 可能是用户定义的类接口的一部分,通用代码应该找到它,否则回退到 std::swap。但这几乎不适用于标准库中的任何其他算法,并且应该始终使用std:: 调用它们。特别是如果您是库作者,不使用 std:: 会引入意想不到的自定义点(如果用户定义具有相同名称的函数,您的代码会巧妙地中断)。
  • @Dave 不,这不可能发生。请参阅call_once 的文档:“在选择函数的上述执行成功完成之前,组中没有调用返回,即不会通过异常退出。”
【解决方案2】:

这正是原子设计的场景类型。通过将结果存储到原子中,您知道编译器无法在设置原子后对任何关键存储或操作进行排序。 Atomics 既用于发出处理器指令原语以确保必要的顺序一致性(例如,用于跨内核的缓存一致性),也用于告诉编译器必须保留哪些语义(并因此限制它可以执行的重新排序类型)。如果您在此处使用原子,则函数是否内联无关紧要,因为编译器所做的任何内联都必须保留原子本身的语义。

您可能也有兴趣研究std::call_once,它也是为这种情况而设计的,更具体地说,是针对多个线程可能需要完成某事但其中一个线程应该完成的情况。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    • 2011-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多