2012-01-02 10 views
2

背景シングルトンパターンの実装として関数内でローカル静的変数を使用する際の問題の1つは、複数のスレッドが同時にこの関数を最初に呼び出すと、静的変数の初期化を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; 
} 

または初期化は、コンストラクタの開始前に、変数のメンバーの初期化と同様に、(宣言/初期化の時点ではない)魔法のように行われていますか?

私の質問は、Xeoの答えによれば、C++ 11はそれだけで対応しているからです。

+0

この編集では、関連性がなくなったため、回答を削除する必要があります。 :P – Xeo

+0

@Xeo申し訳ありませんが、より具体的なはずですが、私はC++ 11のその機能をサポートしていないコンパイラを扱っていますが、私はあなたの答えをupvoted、ありがとう –

答えて

5

C++ 11では、ロックが不要です。並行実行は、静的ローカル変数がすでに初期化されている場合に待機します。

§6.7 [stmt.dcl] p4

変数が初期化されている間に制御が同時に宣言を入力した場合、同時実行は、初期化の完了を待たなければなりません。

§6.7 [stmt.dcl] p4

静的記憶期間を持つすべてのローカルオブジェクト(3.7.1)のゼロ初期化(8.5)である:我々はこれを持っているC++ 03


他の初期化が行われる前に実行されます。定数式で初期化された静的記憶期間を持つ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

+0

ああ、C + + 11救助に。 pre-C++ 11はどうですか? –

+0

@Seth:Pre-C++ 11は、並行実行についても知らない。 :Pあなたのアイデアにコメントを付けるには、並行性については十分に分かりません。 : – Xeo

0

あなたのコードが特に正しいかどうかはわかりませんが、一般的には問題が解決します。

2

クリティカルセクションで初期化をラップすることは確かに役に立ちます!以下のコードを使用して、静的変数が1回だけ初期化されることを確認します。 C++ 03の場合

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

+0

スタック変数にポインタを保存していても、 –

+0

ああ!うん私の悪い。それをヒープに割り当てることができます! –

2

、あなたは状態を更新 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); 
    } 
} 
関連する問題