理解Double-Checked Locking
2017-05-05 本文已影响0人
whosemario
传统的单例模式实现
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;
}
在多线程环境下,这种写法会引起condition race。
多线程基本实现
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;
} // 释放锁
上面这种写法可以解决多线程下的condition race的问题,但性能消耗也会很大
多线程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;
}
这种实现看似可以但问题很多。
pInstance = new Singleton()
一个Singleton的实例化其实包含三步
1. 为Singleton分配空间
2. 调用Singleton的构造函数
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;
}
最关键的是步骤2和3的顺序是未定义的!也就是说,pInstance可能先拿到一个没有调用构造函数的地址,此时另一个线程发现pInstance已经非空了,此时pInstance指向一个脏数据。
即使步骤2和3在编译优化后保证了顺序,对于多Process架构来说,内存同步,也会导致问题。因为在Process A上步骤2和3是有序的修改内存,但是在Process B上发现两个地方的内存修改顺序可能是相反的。
为了解决这个问题,我们使用内存屏障:
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来实现单例
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的实现
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)
看起来非常像double checked的逻辑,但是lll_lock加入了内存屏障。
C++11
基于C++11实现单例就更加简单了
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环境c++版本) - http://blog.csdn.net/cgxrit/article/details/43741771