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?

95177 次浏览

Use pthread_once, which is guaranteed that the initialization function is run once atomically.

(On Mac OS X it uses a spin lock. Don't know the implementation of other platforms.)

TTBOMK, the only guaranteed thread-safe way to do this without locking would be to initialize all your singletons before you ever start a thread.

Your alternative is called "double-checked locking".

There could exist multi-threaded memory models in which it works, but POSIX does not guarantee one

Your solution is called 'double checked locking' and the way you've written it is not threadsafe.

This Meyers/Alexandrescu paper explains 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.

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.

ACE singleton implementation uses double-checked locking pattern for thread safety, you can refer to it if you like.

You can find source code here.

If you are using C++11, here is a right way to do this:

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)

Herb Sutter talks about the double-checked locking in CppCon 2014.

Below is the code I implemented in C++11 based on that:

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

Does TLS work here? 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;
}

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();

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.