1. 程式人生 > >Singleton優化(Double-Checked Locking Pattern)

Singleton優化(Double-Checked Locking Pattern)

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 就不會調換順序執行了。

很多時候,只是用簡單實現就沒問題了。