use*_*538 4 c++ singleton multithreading 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;
}
Run Code Online (Sandbox Code Playgroud)
但为什么他们说这是一个线程安全的实现呢?
例如,第一个线程可以通过 上的两个测试pInstance == 0,创建new Singleton并将其分配给temp指针,然后开始分配pInstance = temp(据我所知,指针分配操作不是原子的)。
同时,第二个线程测试第一个线程pInstance == 0,其中pInstance仅分配了一半。它不是 nullptr,但也不是一个有效的指针,然后从函数返回。这样的情况会发生吗?我在任何地方都没有找到答案,似乎这是一个非常正确的实现,但我什么也不明白
根据 C++ 并发规则,这是不安全的,因为第一次读取pInstance不受锁或类似内容的保护,因此无法与写入(受保护的写入)正确同步。因此存在数据竞争和未定义行为。该 UB 的可能结果之一正是您所确定的:第一个检查读取了垃圾值pInstance刚刚由不同的线程写入。
常见的解释是,在更常见的情况下(pInstance。然而,这并不安全。
由于 C++11 及更高版本保证函数范围静态变量的初始化仅发生一次并且是线程安全的,因此在 C++ 中创建单例的最佳方法是在函数中拥有静态局部变量:
Singleton& Singleton::instance() {
static Singleton s;
return s;
}
Run Code Online (Sandbox Code Playgroud)
请注意,不需要动态分配或指针返回类型。
正如Voo在评论中提到的,上面假设pInstance是一个原始指针。如果是这样std::atomic<Singleton*>,代码就会按预期正常工作。当然,接下来的问题是原子读取是否比获取锁慢很多,这个问题应该通过分析来回答。尽管如此,这仍然是一个毫无意义的练习,因为静态局部变量在所有情况下都更好,除了非常模糊的情况。