理解Double-Checked Locking

傳統(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

  1. C++ and Perils of Double-Checked Locking - http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
  2. 淺析單例模式與線程安全(Linux環(huán)境c++版本) - http://blog.csdn.net/cgxrit/article/details/43741771
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容