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

时间:2017-04-08 10:29:42

标签: c++ multithreading singleton double-checked-locking

我知道线程安全单例的常见实现如下所示:

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指针,然后开始赋值pInstance = temp (据我所知,指针赋值操作不是原子的) 同时第二个线程测试第一个pInstance == 0,其中pInstance只分配了一半。它不是nullptr,但也不是有效的指针,然后从函数返回。 这样的情况会发生吗?我没有在任何地方找到答案,似乎这是一个非常正确的实现,我什么都不懂

1 个答案:

答案 0 :(得分:4)

C ++并发规则并不安全,因为pInstance的第一次读取不受锁或类似的保护,因此不能与写入正确同步( 受保护)。因此存在数据竞争,因此存在未定义的行为。这个UB的一个可能的结果正是你已经确定的:第一次检查读取了pInstance的垃圾值,它只是由不同的线程写的。

常见的解释是,在更常见的情况下(Singleton& Singleton::instance() { static Singleton s; return s; } 已经有效),它可以节省获取锁(可能是时间昂贵的操作)。但是,这不安全。

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

pInstance

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

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