在C ++ 11中进行双重检查锁定?

时间:2013-05-07 06:22:16

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

以下是来自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可以在执行构造函数之前将实例设置为非null,因此当另一个线程检查#4上的实例时,它将不为null并返回一个尚未正确构造的实例。

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

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

2 个答案:

答案 0 :(得分:25)

我会暂时忽略单例位并假设你需要这个用于延迟初始化而不是像单身人士那样愚蠢的东西。

我建议忘记双重检查锁定。 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; });

答案 1 :(得分:2)

这正是原子设计的情况类型。通过将结果存储到原子中,您知道在设置原子之后编译器无法对任何关键存储或操作进行排序。原子既用于发射处理器指令原语以确保必要的顺序一致性(例如,用于跨核心的高速缓存一致性)以及用于告知编译器必须保留哪些语义(因此限制它可以执行的重新排序的类型)。如果你在这里使用一个原子,那么函数是否被内联是无关紧要的,因为无论编译器做什么内联都必须保留原子本身的语义。

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