由於C++不能保證靜態或者全局對象的構造函數的調用順序以及析構順序。所以如果程序中有多個用此方法實現的Singleton類,它們之間又有某種構造依賴關系和析構依賴關系,就會造成災難性的後果。所以,只有當肯定不會有構造和析構依賴關系的情況下,這種實現才是合適的。
> 優點 實現簡單,多線程下安全 > 缺點 如果有多個Singleton對象的創建順序有依賴時,千萬別用;不是lazy loading,有些浪費。Scott Meyer在<<Effective C++>>3rd Item4中提出了一個解決方案,當將non-local static變量移動到靜態方法中成為local static變量的時候。C++保證當第一次靜態方法被調用的時候,才會創建該靜態變量。但是這裡有一個疑問,創建順序能夠被控制了,可是析構順序呢?我們只知道進程結束的時候,local static 變量會被析構,而且按照創建順序的相反順序進行。如果幾個Singleton類的析構函數之間也有依賴關系,並且這種依賴順序關系和LIFO順序沖突,就會造成dead-reference問題。
> 優點 實現簡單;用的時候才創建,比較節省。 > 缺點 多線程下不安全;如果有多個Singleton對象的析構順序有依賴時,要小心DCLP 就是 Double-checked locking pattern.用於在多線程環境下保證只創建Singleton對象。第一次check不用加鎖,但是第二次check和創建對象必須加鎖。還要注意編譯器可能會優化代碼,導致DCLP模式失效。因此要使用volatile 修飾T* pInstance變量。先看一下 DCLP的實現代碼:
> class Singleton { > public: > static Singleton* instance() { > if (pInstance == 0) { > Lock lock; > if (pInstance == 0) { > pInstance = new Singleton; > } > }
> return pInstance; > } > private: > static Singleton * volatile pInstance; > Singleton(){ > } > }; >
在c++98標准下,這是不可靠的。原因有三點:
一,執行順序得不到保證。編譯器會優化代碼,從而改變執行順序。
pInstance = new Singleton; 這個語句會分成三步完成:1.分配內存,2.在已經分配的內存上調用構造函數創建對象,3.將對象賦值給指針pInstance.但是這個順序很可能會被改變為1,3,2。如果A線程在1,3執行完後,B線程執行第一個條件判斷if(pInstance ==0),此時鎖不能起到保護作用。B線程會認為pInstance已經指向有效對象,可以去使用了。嘿嘿,災難發生。主要原因是C++98標准中沒有包含多線程,只假定是單線程,編譯器的優化行為無視多線程環境,因此產生的優化代碼可能會被去掉或者改變順序。我們沒有辦法在98標准的采用標准c++語言來解決這個問題,只能采用平台相關的多線程API和與之兼容的編譯器來解決。因此,從本質上來說,基於98標准,此問題無解。
二,volatile對於執行順序也沒有幫助。
三,多處理器的系統,B處理器看到變量值的順序可能和A處理器寫變量值的順序不一致。
詳細解釋請參考Scott Meyers and Andrei Alexandrescu的論文:http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
技術總是發展的,2011標准的出台給DCLP帶來了曙光,真的還能用麼,拭目以待吧.
讓我們先下個結論:
Andrei說4.11 scott meyer闡述了0x標准下,由於有了線程概念,內存模型,sequence point被sequenced before 和 happens before取代, 有了atomic等等,DCLP又可以復活了。
http://cppandbeyond.com/2011/04/11/session-announcement-the-c0x-memory-model-and-why-you-care/
具體談了什麼呢?我還沒有找到相關的文檔。
於是又回到老土的鎖方案,其實注意一下調用,還是能夠提高效率的。
> class Singleton { > public: > static Singleton* instance() { > Lock lock; > if (pInstance == 0) { > pInstance = new Singleton; > }
> return pInstance; > } > private: > static Singleton * volatile pInstance; > Singleton(){ > } > }; >
客戶調用時,在每個線程的開頭都獲得Singleton* p = Singleton::instance();以後就一直使用這個p變量,應該說還是能有效的降低同步的機會。避免頻繁調用Singleton::instance()->就好。
> 優點 實現簡單,線程安全 > 缺點 客戶需要意識到,並且遵守少調用的原則。