双重检查单例线程的实现是否安全? [英] Is implementation of double checked singleton thread-safe?
问题描述
我知道线程安全单例的常见实现如下所示:
I know that the common implementation of thread-safe singleton looks like this:
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,也不是有效的指针,然后从函数中返回该指针.
这样的情况会发生吗?我在任何地方都找不到答案,似乎这是一个非常正确的实现,我什么都不懂
But why they say that it is a thread-safe implementation?
For example, the first thread can pass both tests on pInstance == 0
, create new Singleton
and assign it to the temp
pointer and then start assignment pInstance = temp
(as far as I know, the pointer assignment operation is not atomic).
At the same time the second thread tests the first pInstance == 0
, where pInstance
is assigned only half. It's not nullptr but not a valid pointer too, which then returned from the function.
Can such a situation happen? I didn't find the answer anywhere and seems that it is a quite correct implementation and I don't understand anything
推荐答案
C ++并发规则并不安全,因为pInstance
的首次读取不受锁或类似内容的保护,因此无法与锁正确同步.写(受 保护).因此,存在数据争用,因此存在未定义的行为.该UB可能产生的结果之一就是您已经确定的结果:第一个检查读取的pInstance
垃圾值只是由另一个线程写入的.
It's not safe by C++ concurrency rules, since the first read of pInstance
is not protected by a lock or something similar and thus does not synchronise correctly with the write (which is protected). There is therefore a data race and thus Undefined Behaviour. One of the possible results of this UB is precisely what you've identified: the first check reading a garbage value of pInstance
which is just being written by a different thread.
通常的解释是,在更常见的情况下(pInstance
已经有效),它省去了获取锁定(可能花费大量时间的操作)的麻烦.但是,这并不安全.
The common explanation is that it saves on acquiring the lock (a potentially time-expensive operation) in the more common case (pInstance
already valid). However, it's not safe.
由于C ++ 11和更高版本保证函数范围静态变量的初始化仅发生一次并且是线程安全的,因此在C ++中创建单例的最佳方法是在函数中具有静态局部变量:
Since C++11 and beyond guarantees initialisation of function-scope static variables happens only once and is thread-safe, the best way to create a singleton in C++ is to have a static local in the function:
Singleton& Singleton::instance() {
static Singleton s;
return s;
}
请注意,不需要动态分配或指针返回类型.
Note that there's no need for either dynamic allocation or a pointer return type.
正如注释中提到的 Voo 一样,以上假设pInstance
是原始指针.如果它是std::atomic<Singleton*>
,则代码将按预期工作.当然,这是一个问题,原子读取是否比获取锁慢得多,应该通过分析来回答.尽管如此,这仍然是一个毫无意义的练习,因为除了非常模糊的情况之外,静态局部变量在所有情况下都更好.
As Voo mentioned in comments, the above assumes pInstance
is a raw pointer. If it was std::atomic<Singleton*>
instead, the code would work just fine as intended. Of course, it's then a question whether an atomic read is that much slower than obtaining the lock, which should be answered via profiling. Still, it would be a rather pointless exercise, since the static local variables is better in all but very obscure cases.
这篇关于双重检查单例线程的实现是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!