双重检查单例线程安全的实现吗?

Is implementation of double checked singleton thread-safe?

我知道线程安全单例的常见实现是这样的:

Singleton* Singleton::instance() {
   if (pInstance == 0) {
      Lock lock;
      if (pInstance == 0) {
         Singleton* temp = new Singleton; // initialize to temp
         pInstance = temp; // assign temp to pInstance
      }
   }
   return pInstance;
}

但为什么他们说它是线程安全的实现?
例如,第一个线程可以通过 pInstance == 0 上的两个测试,创建 new Singleton 并将其分配给 temp 指针,然后 start 赋值 pInstance = temp(据我所知,指针赋值操作不是原子的)。
同时,第二个线程测试第一个 pInstance == 0,其中 pInstance 只分配了一半。它不是 nullptr 但也不是有效指针,然后从函数返回。 这样的情况会发生吗?我在任何地方都找不到答案,似乎这是一个非常正确的实现,我什么都不懂

C++ 并发规则不安全,因为 pInstance 的第一次读取不受锁或类似东西的保护,因此无法与写入( 受保护)。因此存在数据竞争,因此存在未定义的行为。这个 UB 的一个可能结果正是您所确定的:第一个检查读取 pInstance 的垃圾值,它正被另一个线程写入。

常见的解释是,在更常见的情况下(pInstance 已经有效),它节省了获取锁(一个可能耗时的操作)的时间。但是,这并不安全。

由于 C++11 及更高版本保证函数范围静态变量的初始化只发生一次并且是线程安全的,因此在 C++ 中创建单例的最佳方法是在函数中有一个静态局部变量:

Singleton& Singleton::instance() {
   static Singleton s;
   return s;
}

请注意,不需要动态分配或指针 return 类型。


正如评论中提到的Voo,上面假设pInstance是一个原始指针。如果它是 std::atomic<Singleton*>,则代码将按预期正常工作。当然,这是一个原子读取是否比获取锁慢得多的问题,应该通过分析来回答这个问题。尽管如此,这仍然是一个毫无意义的练习,因为静态局部变量在所有情况下都更好,但非常模糊。