[英]efficient thread-safe singleton in C++
单身类的通常模式就像
static Foo &getInst()
{
static Foo *inst = NULL;
if(inst == NULL)
inst = new Foo(...);
return *inst;
}
但是,我的理解是这个解决方案不是线程安全的,因为1)Foo的构造函数可能被多次调用(可能或可能不重要)和2)inst在返回到不同的线程之前可能没有完全构造。
一种解决方案是围绕整个方法包装一个互斥锁,但是在我真正需要它之后很长时间我就要付出同步开销。 另一种选择是
static Foo &getInst()
{
static Foo *inst = NULL;
if(inst == NULL)
{
pthread_mutex_lock(&mutex);
if(inst == NULL)
inst = new Foo(...);
pthread_mutex_unlock(&mutex);
}
return *inst;
}
这是正确的做法,还是我应该注意哪些陷阱? 例如,是否存在可能发生的静态初始化顺序问题,即在第一次调用getInst时,inst总是保证为NULL?
如果您使用的是C ++ 11,这是一种正确的方法:
Foo& getInst()
{
static Foo inst(...);
return inst;
}
根据新标准,不再需要关心这个问题。 对象初始化只能由一个线程完成,其他线程将等待它完成。 或者你可以使用std :: call_once。 (更多信息在这里 )
您的解决方案称为“双重检查锁定”,您编写它的方式不是线程安全的。
这篇Meyers / Alexandrescu论文解释了为什么 - 但这篇论文也被广泛误解。 它开始了'双重检查锁定在C ++中的不安全'模因 - 但它的实际结论是C ++中的双重检查锁定可以安全地实现,它只需要在非显而易见的地方使用内存屏障。
本文包含伪代码,演示如何使用内存屏障来安全地实现DLCP,因此您应该不难纠正您的实现。
Herb Sutter谈到了CppCon 2014中的双重锁定。
下面是我在C ++ 11中实现的代码,基于:
class Foo {
public:
static Foo* Instance();
private:
Foo() {}
static atomic<Foo*> pinstance;
static mutex m_;
};
atomic<Foo*> Foo::pinstance { nullptr };
std::mutex Foo::m_;
Foo* Foo::Instance() {
if(pinstance == nullptr) {
lock_guard<mutex> lock(m_);
if(pinstance == nullptr) {
pinstance = new Foo();
}
}
return pinstance;
}
您也可以在这里查看完整的程序: http : //ideone.com/olvK13
使用pthread_once
,保证初始化函数以原子方式运行一次。
(在Mac OS X上它使用自旋锁。不知道其他平台的实现。)
TTBOMK是唯一保证线程安全的方法,可以在没有锁定的情况下执行此操作,即在启动线程之前初始化所有单例。
您的替代方案称为“双重检查锁定” 。
可能存在多线程内存模型,但POSIX不保证
ACE单例实现使用双重检查锁定模式来确保线程安全,如果您愿意,可以参考它。
你可以在这里找到源代码。
TLS在这里工作吗? https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++
例如,
static _thread Foo *inst = NULL;
static Foo &getInst()
{
if(inst == NULL)
inst = new Foo(...);
return *inst;
}
但我们还需要一种明确删除它的方法,比如
static void deleteInst() {
if (!inst) {
return;
}
delete inst;
inst = NULL;
}
解决方案不是线程安全的,因为声明
inst = new Foo();
可以由编译器分解为两个语句:
Statement1:inst = malloc(sizeof(Foo));
Statement2:inst-> Foo();
假设在执行语句1之后,通过一个线程上下文切换发生。 第二个线程也执行getInstance()
方法。 然后第二个线程将发现'inst'指针不为null。 所以第二个线程将返回指向未初始化对象的指针,因为第一个线程尚未调用构造函数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.