Singleton
简单实现
class Singleton
{
public:
static Singleton* GetInstance()
{
Guard(mutex);
if (!instance)
instance = new Singleton;
return instance;
}
private:
Mutex mutex;
Singleton* volatile instance;
}
使用volatile是防止在多线程初始化时,进行多次new。
优化实现
简单实现每次get实例时都需要获取锁,改进不需获取锁
class Singleton
{
public:
static Singleton* GetInstance()
{
if (!instance)
{
Guard(mutex);
if (!instance)
instance = new Singleton;
}
return instance;
}
private:
Mutex mutex;
Singleton* volatile instance;
}
代码存在一个问题是,new 不是原子操作,被编译后,可分为三个指令:
- Step 1: Allocate memory to hold a Singleton object.
- Step 2: Construct a Singleton object in the allocated memory.
- Step 3: Make pInstance point to the allocated memory
被便宜后,Step 2 和 Step 3可能调换顺序执行;此时,如果第二个线程调用Get,外层判断不为空,就会获取到还没完成构造的类指针。
你可能想到两个办法:
- 定义一个bool flag,new后置为true
- 添加一个临时变量Singleton* tmp
方法一:
static Singleton* GetInstance()
{
if (!flag)
{
Guard(mutex);
if (!flag)
{
instance = new Singleton;
flag = true;
}
}
return instance;
}
因为flag置为true之前,instance已经完全初始化,所以多线程乱序Get没问题。
方法二:
class Singleton
{
public:
static Singleton* GetInstance()
{
if (!instance)
{
Guard(mutex);
if (!instance)
{
Singleton* volatile tmp = new Singleton;
instance = tmp;
}
}
return instance;
}
private:
Mutex mutex;
Singleton* volatile instance;
}
遗憾,这个方法使用tmp依然被编译器优化,tmp相当于没存在一样。
但是有一种办法让编译不能优化掉tmp,就是让instance指向的对象也用volatile修饰。
如下:
class Singleton
{
public:
static Singleton* GetInstance()
{
if (!instance)
{
Guard(mutex);
if (!instance)
{
volatile Singleton* volatile tmp = new Singleton;
instance = tmp;
}
}
return instance;
}
private:
Mutex mutex;
volatile Singleton* volatile instance;
}
这样一来,Step 2 和 Step 3 就不会调换顺序执行了。
很多时候,只是用简单实现就没问题了。
参考:
- Double-Ckecked Locking
- C++ and the Perils of Double-Checked Locking