2012-09-06 100 views
11

考慮到線程安全的雙重檢查鎖定(對於單例或惰性初始化),我已經閱讀了很多問題。在一些線程中,答案是模式完全被破壞,另一些則提出瞭解決方案。C++ 11:用於延遲初始化的安全雙重檢查鎖定。可能?

所以我的問題是:有沒有辦法在C++中編寫完全線程安全的雙重檢查鎖定模式?如果是這樣,它是怎樣的。

我們可以假設C++ 11,如果這樣做更容易。據我所知,C++ 11改進了內存模型,可以產生所需的改進。

我知道在Java中可以通過使用雙重檢查保護變量volatile。由於C++ 11從Java中借用了大部分內存模型,所以我認爲這可能是可能的,但是如何呢?

+5

如果您可以使用C++ 11,則忽略整個雙重檢查的鎖定業務並使用靜態局部變量或'std :: call_once'。 –

+0

靜態本地人是懶惰初始化?關於'call_once':如何確保一次調用不會將未完全創建的引用寫入變量? – gexicide

+3

是的,靜態locals是以線程安全的方式懶惰地初始化的。 'call_once'確保主題只被調用過一次;並且在實際執行該函數的那個​​函數返回之前,沒有其他調用'call_once'返回(您可以在http://en.cppreference.com/w/cpp/thread/call_once處閱讀更多內容)。它如何做到這一點取決於實施。這兩件事基本上存在,所以你不想再寫更多的錯誤雙重檢查鎖定實現。 –

回答

16

只需使用一個靜態的局部變量延遲初始化單身,就像這樣:

MySingleton* GetInstance() { 
    static MySingleton instance; 
    return &instance; 
} 

的(C++ 11)標準已經保證了靜態變量的線程安全的方式初始化,它很可能是這至少和你自己寫的任何東西一樣強大和高效。

初始化的threadsafety可以在(C++ 11)標準的§6.7.4中找到:

如果控制進入聲明同時而可變正在初始化,併發執行應等待初始化完成。

+11

這是瘋了。爲什麼,哦,爲什麼,如果它可以**從不**爲'null',你會返回一個指針嗎? –

+1

@MatthieuM .:主要是因爲它使人們不太願意複製潛在的對象。當然,一個精心設計的單身**不應該有一個複製構造函數,但仍然。我真的不明白在這種情況下,參考價值回報與價值回報有什麼關係。所以我幾乎不會說那種瘋狂。 – Grizzly

+2

@Grizzly:如果一個對象不應該是可複製的,則由該對象來執行。如果一個對象應該有一個全局可訪問的實例,那麼應該有一個函數來處理它(和你的一樣)。這兩件事是分開的,沒有理由將它們結合起來。這就是爲什麼Singleton模式很愚蠢。 – GManNickG

3

既然你想看到一個有效的DCLP C++ 11實現,這裏是一個。

在灰熊的回答中,該行爲完全線程安全並且與GetInstance()完全相同。

std::mutex mtx; 
std::atomic<MySingleton *> instance_p{nullptr}; 

MySingleton* GetInstance() 
{ 
    auto *p = instance_p.load(std::memory_order_acquire); 

    if (!p) 
    { 
     std::lock_guard<std::mutex> lck{mtx}; 

     p = instance_p.load(std::memory_order_relaxed); 
     if (!p) 
     { 
      p = new MySingleton; 
      instance_p.store(p, std::memory_order_release); 
     } 
    } 

    return p; 
} 
+0

實際上,您希望將函數外部的全局變量「mtx」和「instance_p」聲明爲全局變量,而不是函數內的靜態變量,因爲否則您將爲編譯器的內部檢查付出代價在_every_調用時調用'mtx'和'instance_p',從而克服了雙重檢查鎖定的問題(因爲在性能方面你可能只是將單例聲明爲靜態)。 – BeeOnRope

+0

@BeeOnRope有效的點..我已經做了這個改變。 – LWimsey

+0

你可能想補充一點,你基本上不想寫這個代碼。 Grizzly的回答更加簡潔,加上編譯器可能會在未來插入更多的魔法,使其比此代碼更快。 – gexicide