Windowsでスレッドセーフなシングルトンパターンを作成するにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/164496

  •  03-07-2019
  •  | 
  •  

質問

ここでスレッドセーフシングルトンパターンについて読んでいます:

http://en.wikipedia.org/wiki /Singleton_pattern#C.2B.2B_.28using_pthreads.29

そして最後に、唯一の安全な方法はpthread_onceを使用することであると述べています-これはWindowsでは利用できません。

スレッドセーフな初期化を保証するのは唯一の方法ですか?

SOでこのスレッドを読みました:

C ++でのシングルトンのスレッドセーフな遅延構築

そして、アトミックOSレベルのスワップと比較機能を示唆しているようです。

http://msdn.microsoft.com/en-us/library /ms683568.aspx

これは私が望むことをすることができますか?

編集:遅延初期化を行い、クラスのインスタンスが1つだけになるようにします。

名前空間内でグローバルを使用して言及された別のサイトの誰か(そして彼はシングルトンをアンチパターンとして説明しました)-それはどうして<!> quot; anti-pattern <!> quot;?

受け入れられる回答:
ジョシュの回答 Visual Studio 2008を使用している場合-注:将来の読者のために、このコンパイラ(または2005)を使用していない場合-受け入れられた答えを使用しないでください!!

編集: returnステートメントを除き、コードは正常に動作します-エラーが表示されます: エラーC2440: 'return': 'volatile Singleton *'から 'Singleton *'に変換できません。 戻り値を揮発性シングルトン*に変更する必要がありますか?

編集:明らかにconst_cast <!> lt; <!> gt; volatile修飾子を削除します。再びジョシュに感謝します。

役に立ちましたか?

解決

Visual C ++ 2005/2008を使用している場合は、<!> quot; 揮発性変数はフェンスとして動作します <!> quot;。これは、遅延初期化シングルトンを実装する最も効率的な方法です。

MSDN Magazineから:

Singleton* GetSingleton()
{
    volatile static Singleton* pSingleton = 0;

    if (pSingleton == NULL)
    {
        EnterCriticalSection(&cs);

        if (pSingleton == NULL)
        {
            try
            {
                pSingleton = new Singleton();
            }
            catch (...)
            {
                // Something went wrong.
            }
        }

        LeaveCriticalSection(&cs);
    }

    return const_cast<Singleton*>(pSingleton);
}

シングルトンへのアクセスが必要な場合は、GetSingleton()を呼び出すだけです。初めて呼び出されると、静的ポインターが初期化されます。初期化された後、NULLチェックはポインタを読み取るだけのロックを防ぎます。

移植性がないため、どのコンパイラでもこれを使用しないでください。この規格は、これがどのように機能するかについて保証しません。 Visual C ++ 2005は、これを可能にするためにvolatileのセマンティクスに明示的に追加します。

宣言してクリティカルセクションを初期化する必要があります。 コードの他の場所。ただし、その初期化は安価であるため、通常、遅延初期化は重要ではありません。

他のヒント

シングルトンのクロスプラットフォームスレッドセーフ初期化を保証する簡単な方法は、アプリケーションのメインスレッドで明示的に(シングルトンの静的メンバー関数の呼び出しを介して)実行することです< strong>前アプリケーションが他のスレッド(または少なくともシングルトンにアクセスする他のスレッド)を開始します。

ミューテックス/クリティカルセクションを使用して、シングルトンへのスレッドセーフアクセスを通常の方法で実現します。

遅延初期化も同様のメカニズムを使用して実現できます。これで遭遇する通常の問題は、スレッド安全性を提供するために必要なミューテックスがシングルトン自体で初期化されることが多く、これがスレッド安全性の問題をミューテックス/クリティカルセクションの初期化にプッシュすることです。この問題を解決する1つの方法は、アプリケーションのメインスレッドでmutex / criticalセクションを作成および初期化し、静的メンバー関数の呼び出しを介してシングルトンに渡すことです。その後、この事前に初期化されたミューテックス/クリティカルセクションを使用して、シングルトンの重い初期化をスレッドセーフな方法で行うことができます。例:

// A critical section guard - create on the stack to provide 
// automatic locking/unlocking even in the face of uncaught exceptions
class Guard {
    private:
        LPCRITICAL_SECTION CriticalSection;

    public:
        Guard(LPCRITICAL_SECTION CS) : CriticalSection(CS) {
            EnterCriticalSection(CriticalSection);
        }

        ~Guard() {
            LeaveCriticalSection(CriticalSection);
        }
};

// A thread-safe singleton
class Singleton {
    private:
        static Singleton* Instance;
        static CRITICAL_SECTION InitLock;
        CRITICIAL_SECTION InstanceLock;

        Singleton() {
            // Time consuming initialization here ...

            InitializeCriticalSection(&InstanceLock);
        }

        ~Singleton() {
            DeleteCriticalSection(&InstanceLock);
        }

    public:
        // Not thread-safe - to be called from the main application thread
        static void Create() {
            InitializeCriticalSection(&InitLock);
            Instance = NULL;
        }

        // Not thread-safe - to be called from the main application thread
        static void Destroy() {
            delete Instance;
            DeleteCriticalSection(&InitLock);
        }

        // Thread-safe lazy initializer
        static Singleton* GetInstance() {
            Guard(&InitLock);

            if (Instance == NULL) {
                Instance = new Singleton;
            }

            return Instance;
        }

        // Thread-safe operation
        void doThreadSafeOperation() {
            Guard(&InstanceLock);

            // Perform thread-safe operation
        }
};

ただし、シングルトンの使用を完全に回避する正当な理由(およびそれらがアンチパターンと呼ばれることもあります):

  • これらは基本的にグローバル化されたグローバル変数です
  • これらは、アプリケーションの異なる部分間の高い結合を引き起こす可能性があります
  • ユニットテストをより複雑または不可能にすることができます(実際のシングルトンと偽の実装を交換するのが難しいため)

別の方法は、メインスレッドでクラスの単一インスタンスを作成および初期化し、それを必要とするオブジェクトに渡す「論理シングルトン」を利用することです。このアプローチは、シングルトンとして作成するオブジェクトが多数ある場合、扱いにくくなる可能性があります。この場合、異なるオブジェクトを単一の「Context」オブジェクトにバンドルし、必要に応じて渡すことができます。

承認済みのソリューションが気に入っているのに、別の有望なリードを見つけたので、ここで共有する必要があると考えました:ワンタイム初期化(Windows)

mutexやクリティカルセクションなどのOSプリミティブを使用してスレッドセーフな初期化を保証できますが、シングルトンポインターにアクセスするたびにオーバーヘッドが発生します(ロックを取得するため)。移植性もありません。

この質問について考慮する必要がある明確なポイントが1つあります。必要ですか...

  1. 実際に作成されるクラスのインスタンスは1つだけです
  2. クラスの多くのインスタンスを作成できますが、クラスの真の決定的なインスタンスは1つだけでなければなりません

これらのパターンをC ++で実装するためのサンプルがWeb上に多数あります。 コードプロジェクトのサンプル

以下ではC#でそれを行う方法を説明しますが、シングルトンパターンをサポートするプログラミング言語にはまったく同じ概念が適用されます

http://www.yoda.arachsys.com/csharp/singleton.html

決定する必要があるのは、遅延初期化が必要かどうかです。遅延初期化とは、シングルトン内に含まれるオブジェクトが最初の呼び出しで作成されることを意味します 例:

MySingleton::getInstance()->doWork();

その呼び出しが後まで行われない場合、記事で説明されているように、スレッド間の競合状態の危険があります。ただし、置く場合

MySingleton::getInstance()->initSingleton();

コードの最初で、スレッドセーフであると想定している場合、遅延初期化はもう必要ありません。<!> quot; some <!> quot;アプリケーションの起動時に処理能力が向上します。ただし、そうすることで競合状態に関する多くの頭痛の種を解決できます。

よりポータブルで簡単なソリューションを探している場合は、ブーストを選択できます。

boost :: call_once を使用できますスレッドセーフな初期化のため。

使い方は非常に簡単で、次のC ++ 0x標準の一部になります。

質問は、シングルトンが遅延構築されるかどうかを要求しません。 多くの答えがそれを前提としているので、最初のフレーズについて議論するものと仮定します:

言語自体がスレッド認識ではなく、最適化手法に加えて、移植性のある信頼できるc ++シングルトンを書くのは非常に難しい(不可能ではないにしても)ことを考えると、<!> quot; C ++とダブルチェックロックの危険 <!> quot;スコット・マイヤーズとアンドレイ・アレクサンドレスク。

CriticalSectionを使用して、Windowsプラットフォームでオブジェクトを同期するという回答の多くを見てきましたが、CriticalSectionは、すべてのスレッドが1つのシングルプロセッサで実行されている場合のみスレッドセーフです。

MSDN cite:<!> quot;単一プロセスのスレッドは、相互排除同期にクリティカルセクションオブジェクトを使用できます。 <!> quot;。

および http:/ /msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx

さらに明確にする:

クリティカルセクションオブジェクトは、1つのプロセスのスレッドのみがクリティカルセクションを使用できることを除いて、mutexオブジェクトが提供するものと同様の同期を提供します。

今、<!> quot; lazy-constructed <!> quot;必須ではありません。次のソリューションは、クロスモジュールセーフとスレッドセーフの両方であり、移植性もあります。

struct X { };

X * get_X_Instance()
{
    static X x;
    return &x;
}
extern int X_singleton_helper = (get_X_instance(), 1);

ファイル/名前空間スコープのグローバルオブジェクトの代わりにローカルスコープの静的オブジェクトを使用するため、クロスモジュールセーフです。

次の理由により、スレッドセーフです:mainまたはDllMainを入力する前に、X_singleton_helperを正しい値に割り当てる必要があります。この事実のために遅延構築されません)、この式では、コンマは句読点ではなく演算子です。

<!> quot; extern <!> quot;を明示的に使用します。コンパイラーが最適化するのを防ぐために(Scott Meyersの記事に関する懸念、最大の敵はオプティマイザーです)、またpc-lintなどの静的分析ツールを黙らせます。 <!> quot; main / DllMain <!> quotの前; <!> quot;シングルスレッド起動部<!> quotと呼ばれるScott meyerです。 in <!> quot; Effective C ++ 3rd <!> quot;アイテム4。

ただし、コンパイラが言語標準に従ってget_X_instance()の呼び出しを最適化できるかどうかはよくわかりません。コメントしてください。

Windowsでスレッドセーフなシングルトン*初期化を行う方法はたくさんあります。実際、それらのいくつかはクロスプラットフォームです。あなたがリンクしたSOスレッドでは、彼らはCで怠ziに構築されたシングルトンを探していました。これはもう少し具体的で、あなたが働いているメモリモデルの複雑さを考えると、正しいことをするのが少し難しい可能性があります。

  • 決して使用すべきではない
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top