7

这是来自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应该这样做,但我很肯定我错了。

4

2 回答 2

26

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

我建议忘记双重检查锁定。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; });
于 2013-05-07T14:18:48.317 回答
2

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

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

于 2013-05-07T14:11:39.963 回答