Singleton優化(Double-Checked Locking Pattern)
阿新 • • 發佈:2019-02-17
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 就不會調換順序執行了。
很多時候,只是用簡單實現就沒問題了。