单例类的常用模式类似于
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?
答案 0 :(得分:81)
如果您使用的是C ++ 11,这是一种正确的方法:
Foo& getInst()
{
static Foo inst(...);
return inst;
}
根据新标准,不再需要关心这个问题。对象初始化只能由一个线程完成,其他线程将等待它完成。 或者你可以使用std :: call_once。 (更多信息here)
答案 1 :(得分:41)
您的解决方案称为“双重检查锁定”,您编写它的方式不是线程安全的。
这Meyers/Alexandrescu paper解释了原因 - 但该论文也被广泛误解。它启动了“双重检查锁定在C ++中不安全”的模式 - 但它的实际结论是,C ++中的双重检查锁定可以安全地实现,它只需要在非显而易见的地方使用内存屏障。
本文包含伪代码,演示了如何使用内存屏障来安全地实现DLCP,因此您应该不难纠正您的实现。
答案 2 :(得分:11)
Herb Sutter talks about the double-checked locking in 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
答案 3 :(得分:8)
使用pthread_once
,保证初始化函数以原子方式运行。
(在Mac OS X上,它使用自旋锁。不知道其他平台的实现。)
答案 4 :(得分:2)
TTBOMK,唯一保证线程安全的方法是在没有锁定的情况下执行此操作,而是在启动线程之前初始化所有单身。
答案 5 :(得分:0)
您的替代方案称为"double-checked locking"。
可能存在多线程内存模型,但是POSIX不能保证一个
答案 6 :(得分:0)
ACE单例实现使用双重检查锁定模式来确保线程安全,如果您愿意,可以参考它。
您可以找到源代码here。
答案 7 :(得分:0)
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;
}
答案 8 :(得分:-1)
该解决方案不是线程安全的,因为该语句
inst = new Foo();
可由编译器分解为两个语句:
Statement1:inst = malloc(sizeof(Foo));
声明2:inst-> Foo();
假设由一个线程执行语句1之后,发生上下文切换。第二线程也执行getInstance()
方法。然后,第二个线程将发现“ inst”指针不为空。因此,由于第一个线程尚未调用构造函数,因此第二个线程将返回指向未初始化对象的指针。