傳統(tǒng)的單例模式實(shí)現(xiàn)
class Singleton {
public:
static Singleton* instance();
...
private:
static Singleton* pInstance;
};
Singleton* Singleton::pInstance = 0;
Singleton* Singleton::instance() {
if(pInstance == 0) {
pInstance = new Singleton();
}
return pInstance;
}
在多線程環(huán)境下,這種寫(xiě)法會(huì)引起condition race。
多線程基本實(shí)現(xiàn)
class Singleton {
public:
static Singleton* instance();
...
private:
static Singleton* pInstance;
};
Singleton* Singleton::pInstance = 0;
Singleton* Singleton::instance() {
Lock lock; // 獲取鎖
if(pInstance == 0) {
pInstance = new Singleton();
}
return pInstance;
} // 釋放鎖
上面這種寫(xiě)法可以解決多線程下的condition race的問(wèn)題,但性能消耗也會(huì)很大
多線程double-check
class Singleton {
public:
static Singleton* instance();
...
private:
static Singleton* pInstance;
};
Singleton* Singleton::pInstance = 0;
Singleton* Singleton::instance() {
if(pInstance == 0) {
Lock lock; // 獲取鎖
if(pInstance == 0) {
pInstance = new Singleton();
}
} // 釋放鎖
return pInstance;
}
這種實(shí)現(xiàn)看似可以但問(wèn)題很多。
pInstance = new Singleton()
一個(gè)Singleton的實(shí)例化其實(shí)包含三步
1. 為Singleton分配空間
2. 調(diào)用Singleton的構(gòu)造函數(shù)
3. 將空間的地址賦值給pInstance
這樣上面的代碼就改造為先的形式:
Singleton* Singleton::instance() {
if(pInstance == 0) {
Lock lock; // 獲取鎖
if(pInstance == 0) {
pInstance = // Step 3
operator new (sizeof(Singleton)); // Step 1
new (pInstance) Singleton; // Step 2
}
} // 釋放鎖
return pInstance;
}
最關(guān)鍵的是步驟2和3的順序是未定義的!也就是說(shuō),pInstance可能先拿到一個(gè)沒(méi)有調(diào)用構(gòu)造函數(shù)的地址,此時(shí)另一個(gè)線程發(fā)現(xiàn)pInstance已經(jīng)非空了,此時(shí)pInstance指向一個(gè)臟數(shù)據(jù)。
即使步驟2和3在編譯優(yōu)化后保證了順序,對(duì)于多Process架構(gòu)來(lái)說(shuō),內(nèi)存同步,也會(huì)導(dǎo)致問(wèn)題。因?yàn)樵赑rocess A上步驟2和3是有序的修改內(nèi)存,但是在Process B上發(fā)現(xiàn)兩個(gè)地方的內(nèi)存修改順序可能是相反的。
為了解決這個(gè)問(wèn)題,我們使用內(nèi)存屏障:
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance;
... // insert memory barrier
if (tmp == NULL) {
Lock lock;
tmp = m_instance;
if (tmp == NULL) {
tmp = new Singleton;
... // insert memory barrier
m_instance = tmp;
}
}
return tmp;
}
C++11前--pthread_once
C++11前比較通用的方式是使用pthread_once來(lái)實(shí)現(xiàn)單例
class Singleton {
public:
static Singleton* instance();
static void init() {
pIntance = new Singleton;
}
...
private:
static Singleton* pInstance;
static pthread_once_t ponce_;
};
Singleton* Singleton::pInstance = 0;
pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;
Singleton* Singleton::instance() {
pthread_once(ponce_, &Singleton::init);
return pInstance;
}
可以看一下pthread_once的實(shí)現(xiàn)
static int once_lock = LLL_LOCK_INITIALIZER;
int
__pthread_once (once_control, init_routine)
pthread_once_t *once_control;
void (*init_routine) (void);
{
/* XXX Depending on whether the LOCK_IN_ONCE_T is defined use a
global lock variable or one which is part of the pthread_once_t
object. */
if (*once_control == PTHREAD_ONCE_INIT)
{
lll_lock (once_lock, LLL_PRIVATE);
/* XXX This implementation is not complete. It doesn't take
cancelation and fork into account. */
if (*once_control == PTHREAD_ONCE_INIT)
{
init_routine ();
*once_control = !PTHREAD_ONCE_INIT;
}
lll_unlock (once_lock, LLL_PRIVATE);
}
return 0;
}
strong_alias (__pthread_once, pthread_once)
hidden_def (__pthread_once)
看起來(lái)非常像double checked的邏輯,但是lll_lock加入了內(nèi)存屏障。
C++11
基于C++11實(shí)現(xiàn)單例就更加簡(jiǎn)單了
class Singleton {
public:
static Singleton* instance() {
static Singleton inst;
return &inst;
}
...
};
References
- C++ and Perils of Double-Checked Locking - http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
- 淺析單例模式與線程安全(Linux環(huán)境c++版本) - http://blog.csdn.net/cgxrit/article/details/43741771