C++ 中高效的线程安全单例
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2576022/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
efficient thread-safe singleton in C++
提问by user168715
The usual pattern for a singleton class is something like
单例类的通常模式是这样的
static Foo &getInst()
{
static Foo *inst = NULL;
if(inst == NULL)
inst = new Foo(...);
return *inst;
}
However, it's my understanding that this solution is not thread-safe, since 1) Foo's constructor might be called more than once (which may or may not matter) and 2) inst may not be fully constructed before it is returned to a different thread.
然而,我的理解是这个解决方案不是线程安全的,因为 1) Foo 的构造函数可能被调用多次(这可能重要也可能无关紧要)和 2)在返回到不同的线程之前可能没有完全构造 inst .
One solution is to wrap a mutex around the whole method, but then I'm paying for synchronization overhead long after I actually need it. An alternative is something like
一种解决方案是在整个方法周围包装一个互斥锁,但是在我真正需要它很久之后我才支付同步开销。另一种选择是
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;
}
Is this the right way to do it, or are there any pitfalls I should be aware of? For instance, are there any static initialization order problems that might occur, i.e. is inst always guaranteed to be NULL the first time getInst is called?
这是正确的方法吗,还是有什么我应该注意的陷阱?例如,是否有任何可能发生的静态初始化顺序问题,即第一次调用 getInst 时 inst 总是保证为 NULL?
采纳答案by JoeG
Your solution is called 'double checked locking' and the way you've written it is not threadsafe.
您的解决方案称为“双重检查锁定”,您编写它的方式不是线程安全的。
This Meyers/Alexandrescu paperexplains why - but that paper is also widely misunderstood. It started the 'double checked locking is unsafe in C++' meme - but its actual conclusion is that double checked locking in C++ can be implemented safely, it just requires the use of memory barriers in a non-obvious place.
这篇Meyers/Alexandrescu 论文解释了原因——但该论文也被广泛误解。它开始了“C++ 中的双重检查锁定是不安全的”模因——但它的实际结论是,C++ 中的双重检查锁定可以安全地实现,它只需要在不明显的地方使用内存屏障。
The paper contains pseudocode demonstrating how to use memory barriers to safely implement the DLCP, so it shouldn't be difficult for you to correct your implementation.
该论文包含演示如何使用内存屏障安全实现 DCP 的伪代码,因此您应该不难更正您的实现。
回答by Sat
If you are using C++11, here is a right way to do this:
如果您使用的是 C++11,这是一个正确的方法:
Foo& getInst()
{
static Foo inst(...);
return inst;
}
According to new standard there is no need to care about this problem any more. Object initialization will be made only by one thread, other threads will wait till it complete. Or you can use std::call_once. (more info here)
按照新标准,这个问题不用再关心了。对象初始化只会由一个线程进行,其他线程将等待它完成。或者您可以使用 std::call_once。(更多信息在这里)
回答by qqibrow
Herb Sutter talks about the double-checked locking in CppCon 2014.
Herb Sutter 在 CppCon 2014 中谈到了双重检查锁定。
Below is the code I implemented in C++11 based on that:
下面是我基于此在 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;
}
you can also check complete program here: http://ideone.com/olvK13
你也可以在这里查看完整的程序:http: //ideone.com/olvK13
回答by kennytm
Use pthread_once
, which is guaranteed that the initialization function is run once atomically.
使用pthread_once
,保证初始化函数原子地运行一次。
(On Mac OS X it uses a spin lock. Don't know the implementation of other platforms.)
(在 Mac OS X 上它使用自旋锁。不知道其他平台的实现。)
回答by sbi
TTBOMK, the only guaranteed thread-safe way to do this without locking would be to initialize all your singletons beforeyou ever start a thread.
TTBOMK,唯一保证线程安全且无需锁定的方法是在启动线程之前初始化所有单例。
回答by Steve Jessop
Your alternative is called "double-checked locking".
您的替代方案称为“双重检查锁定”。
There could exist multi-threaded memory models in which it works, but POSIX does not guarantee one
可能存在它工作的多线程内存模型,但 POSIX 不保证一个
回答by Joe C
Does TLS work here? https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++
TLS 在这里工作吗?https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++
For example,
例如,
static _thread Foo *inst = NULL;
static Foo &getInst()
{
if(inst == NULL)
inst = new Foo(...);
return *inst;
}
But we also need a way to delete it explicitly, like
但是我们还需要一种方法来显式删除它,比如
static void deleteInst() {
if (!inst) {
return;
}
delete inst;
inst = NULL;
}
回答by baris.aydinoz
回答by Ishekk ?
The solution is not thread safe because the statement
解决方案不是线程安全的,因为语句
inst = new Foo();
can be broken down into two statements by compiler:
可以被编译器分解为两个语句:
Statement1: inst = malloc(sizeof(Foo));
Statement2: inst->Foo();
语句 1:inst = malloc(sizeof(Foo));
语句2:inst->Foo();
Suppose that after execution of statement 1 by one thread context switch occurs. And 2nd thread also executes the getInstance()
method. Then the 2nd thread will find that the 'inst' pointer is not null. So 2nd thread will return pointer to an uninitialized object as constructor has not yet been called by the 1st thread.
假设在一个线程执行语句 1 后发生上下文切换。第二个线程也执行该getInstance()
方法。然后第二个线程会发现'inst'指针不为空。因此,由于第一个线程尚未调用构造函数,因此第二个线程将返回指向未初始化对象的指针。