2012-01-02 27 views
2

背景:在函數中使用局部靜態變量作爲單例模式的實現的一個問題是,如果多個線程第一次同時調用該函數,靜態變量的初始化可以進行兩次。關鍵部分和單例模式

我的問題是,如果您將靜態變量的初始化封裝在臨界區中,是否會阻止雙重初始化的發生?例如:

CRITICAL_SECTION cs; 

Class get_class_instance() { 
    EnterCriticalSection(&cs); 

    // is the initialisation of c done inside the critical section? 
    static Class c = Class(data); 

    LeaveCriticalSection(&cs); 

    return c; 
} 

或者被初始化(在聲明/初始化點不)神奇完成,就像變量部件的構造開始前初始化?

我的問題是關於pre-C++ 11的具體問題,因爲根據Xeo的回答,C++ 11自己處理這個問題。

+0

通過編輯,我應該刪除我的答案,因爲它不再相關。 :P – Xeo 2012-01-02 03:49:36

+0

@Xeo對不起,我應該更具體一些,但是我正在處理一個不支持C++ 11的功能的編譯器,儘管我贊成你的回答,謝謝 – 2012-01-02 03:50:31

回答

5

C++ 11不需要鎖定。如果一個靜態局部變量已經被初始化,併發執行應該等待。

§6.7 [stmt.dcl] p4

如果控制進入的同時,而變量被初始化的聲明,併發執行必須等待初始化完成。


對於C++ 03,我們有這樣的:

§6.7 [stmt.dcl] p4

具有靜態存儲持續時間的所有本地對象(3.7.1)的零初始化(8.5)是在進行任何其他初始化之前執行。在第一次輸入塊之前,初始化具有用常量表達式初始化的靜態存儲持續時間的POD類型(3.9)的本地對象。允許實現在靜態存儲持續時間內執行其他本地對象的早期初始化,條件允許在允許實現靜態初始化靜態存儲持續時間在命名空間範圍(3.6.2)中的對象的情況下執行。 否則當第一次控制通過其聲明時,這樣的對象被初始化;

最後一部分很重要,因爲它適用於您的代碼。當控制首先進入get_class_instance()時,它首先通過臨界區的初始化,然後通過單例聲明(因爲它將在臨界區內初始化它),然後將通過臨界區的去初始化。

因此從理論的角度來看,你的代碼應該是安全的。

現在,這可以改進,但不要在每個函數調用中輸入關鍵部分。 @Chethan的基本思想是健全的,所以我們將以此爲基礎。但是,我們也要避免動態分配。對於這一點,不過,我們依靠Boost.Optional:

#include <boost/optional.hpp> 

Class& get_class_instance() { 
    static boost::optional<Class> c; 
    static bool inited; 

    if (!inited){ 
     EnterCriticalSection(&cs); 

     if(!c) 
      c = Class(data); 

     LeaveCriticalSection(&cs); 
     inited = true; 
    } 

    return *c; 
} 

Boost.Optional避免了默認的初始化,並仔細檢查進入避免對每個函數調用的關鍵部分。然而,該版本在作業中引入了對Class的拷貝構造函數的調用。解決方案是就地工廠:

#include <boost/utility/in_place_factory.hpp> 
#include <boost/optional.hpp> 

Class& get_class_instance() { 
    static boost::optional<Class> c; 
    static bool inited; 

    if (!inited){ 
     EnterCriticalSection(&cs); 

     if(!c) 
      c = boost::in_place(data); 

     LeaveCriticalSection(&cs); 
     inited = true; 
    } 

    return *c; 
} 

我感謝去@R。 Martinho Fernandes和@Ben Voigt合作完成這一最終解決方案。如果您對此過程感興趣,請隨時查看transcript。現在


,如果你的編譯器支持一些C++ 11的功能了,但不是靜態初始化的東西,你也可以使用std::unique_ptr與佈局新和靜態對齊的緩衝,結合:

#include <memory> // std::unique_ptr 
#include <type_traits> // alignment stuff 

template<class T> 
struct destructor{ 
    void operator(T* p) const{ 
    if(p) // don't destruct a null pointer 
     p->~T(); 
    } 
}; 

Class& get_class_instance() { 
    typedef std::aligned_storage<sizeof(Class), 
     std::alignment_of<Class>::value>::type storage_type; 
    static storage_type buf; 
    static std::unique_ptr<Class, destructor> p; 
    static bool inited; 

    if (!inited){ 
     EnterCriticalSection(&cs); 

     if(!p) 
      p.reset(new (&buf[0]) Class(data)); 

     LeaveCriticalSection(&cs); 
     inited = true; 
    } 

    return *p; 
} 
+0

對靜態類也是如此會員? – littleadv 2012-01-02 03:43:39

+0

啊,C++ 11來拯救。那麼pre-C++ 11呢? – 2012-01-02 03:44:50

+0

@Seth:Pre-C++ 11甚至不知道併發執行。 :P我不太瞭解併發性,然後對你的想法發表評論。 ( – Xeo 2012-01-02 03:46:45

0

我不確定你的代碼具體是正確的,但通常是的,它會解決問題。

2

用臨界區包裝初始化肯定會有幫助!我會使用下面的代碼來確保我們的靜態變量只被初始化一次。

CRITICAL_SECTION cs; 

Class& get_class_instance() { 
    static Class *c; //by default, global and static variables are assigned default values. Hence c will be NULL when the program starts. 

    EnterCriticalSection(&cs); 

    if(c == NULL) 
     c = new Class(data); 

    LeaveCriticalSection(&cs); 

    return *c; 
} 
+0

是的,這段代碼更有意義 – littleadv 2012-01-02 03:54:35

+0

即使你保存了一個指向堆棧變量的指針 – 2012-01-02 03:55:52

+0

哦!是的。我的錯。讓我們把它分配在堆上! – 2012-01-02 03:57:25

2

對於C++ 03,你需要locklessly更新的狀態。這是因爲,如果不初始化它,就不能使用鎖,因爲您需要線程安全地初始化要用於線程安全初始化的鎖,所以會再次遞歸地運行完全相同的問題。哎呦。您只能依靠零初始化來設置全局變量和一些無鎖指令。這也快得多。

您可以使用狀態變量和無鎖CAS指令來解決此問題。

enum State { 
    ObjectUninitialized, 
    ObjectInitializing, 
    ObjectInitialized 
}; 

volatile std::size_t state; // Must be initialized to 0- done by compiler 
          // before main(). All globals are 0 
          // Also must be word sized 

T* func() { 
    static char buffer[sizeof(T)]; 
    long result = InterlockedCompareExchange(&state, ObjectInitializing, ObjectUninitialized); 
    if (result == ObjectInitialized) { 
     return reinterpret_cast<T*>(buffer); 
    } 
    if (result == ObjectInitializing) { 
     while (state == ObjectInitializing); // read is atomic for word-size variables 
     return reinterpret_cast<T*>(buffer); // and use volatile to force compiler to add  
    } 
    if (result == ObjectUninitialized) { 
     new (buffer) T(); 
     InterlockedExchange(&state, ObjectInitialized); 
     return reinterpret_cast<T*>(buffer); 
    } 
}