2

背景: 関数内でローカル静的変数をシングルトン パターンの実装として使用する際の問題の 1 つは、複数のスレッドが同時に関数を初めて呼び出すと、静的変数の初期化が行われる可能性があることです。二回。

私の質問は、静的変数の初期化を重要なセクションでラップすると、二重の初期化が発生するのを防ぐことができるでしょうか? 例:

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はこれを単独で処理するため、私の質問は特にC++ 11以前に関するものです。

4

4 に答える 4

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。このプロセスに興味がある場合は、気軽にトランスクリプトをご覧ください。


ここで、コンパイラが C++11 機能の一部を既にサポートしているが、静的初期化機能をサポートしていない場合はstd::unique_ptr、placement new と静的に整列されたバッファーを組み合わせて使用​​することもできます。

#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;
}
于 2012-01-02T03:41:46.103 に答える
2

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

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;
}
于 2012-01-02T03:47:03.307 に答える
2

C++03 の場合、ロックなしで状態を更新する必要があります。これは、ロックを初期化せずに使用することはできず、スレッドセーフ初期化に使用するロックをスレッドセーフ初期化する必要があるため、まったく同じ問題が再帰的に発生するためです。おっと。グローバルのゼロ初期化と一部のロックレス命令のみに依存できます。これもはるかに高速です。

この問題は、状態変数とロックレス 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);
    }
}
于 2012-01-02T21:11:48.340 に答える
0

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

于 2012-01-02T03:41:59.420 に答える