16

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

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

そして一番下には、Windows では利用できない pthread_once を使用することが唯一の安全な方法であると書かれています。

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

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

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

そして、アトミックOSレベルのスワップと比較機能を暗示しているようです.Windowsでは次のようになります。

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

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

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

別のサイトの誰かが名前空間内でグローバルを使用することに言及しました (そして彼はシングルトンをアンチパターンと説明しました) - どうすればそれが「アンチパターン」になるのでしょうか?

受け入れられた回答: Visual Studio 2008 を使用しているため、 Josh の回答
を受け入れました- NB: 今後の読者のために、このコンパイラ (または 2005) を使用していない場合 - 受け入れられた回答を使用しないでください!!

編集: コードは return ステートメントを除いて正常に動作します - エラーが発生します: error C2440: 'return' : cannot convert from 'volatile Singleton *' to 'Singleton *'. 戻り値を volatile Singleton * に変更する必要がありますか?

編集:どうやら const_cast<> は volatile 修飾子を削除します。ジョシュに再び感謝します。

4

9 に答える 9

13

シングルトンのクロスプラットフォーム スレッド セーフな初期化を保証する簡単な方法は、アプリケーションが他のスレッドを開始する前に(または少なくともシングルトンにアクセスする他のスレッド)。

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

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

// 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
        }
};

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

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

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

于 2008-10-02T20:51:04.913 に答える
11

Visual C ++ 2005/2008を使用している場合は、「揮発性変数はフェンスとして動作する」ため、二重チェックのロックパターンを使用できます。これは、遅延初期化されたシングルトンを実装するための最も効率的な方法です。

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のセマンティクスを明示的に追加します。

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

于 2008-10-02T21:12:58.903 に答える
4

私は受け入れられた解決策が好きですが、別の有望なリードを見つけたので、ここで共有する必要があると思いました:ワンタイム初期化 (Windows)

于 2012-04-20T20:12:12.463 に答える
1

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

于 2008-10-02T20:51:37.143 に答える
1

この質問に対して考慮すべき点が 1 つあります。必要ですか...

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

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

于 2008-10-02T20:53:23.210 に答える
0

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

boost::call_onceは、スレッド セーフな初期化に使用できます。

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

于 2008-10-21T19:40:22.947 に答える
0

質問は、シングルトンが遅延構築されているかどうかを必要としません。多くの回答がそれを前提としているため、最初のフレーズについては次のように説明していると思います。

言語自体がスレッド認識ではないという事実と、それに加えて最適化手法を考慮すると、移植性の高い信頼性の高い C++シングルトンを作成することは (不可能ではないにしても) 非常に困難です。アンドレイ・アレクサンドレスク。

CriticalSection を使用して Windows プラットフォーム上でオブジェクトを同期するという解決手段の多くを見てきましたが、CriticalSection は、すべてのスレッドが 1 つのプロセッサで実行されている場合にのみスレッド セーフになります。

MSDN の引用: 「単一プロセスのスレッドは、相互排除同期のためにクリティカル セクション オブジェクトを使用できます。」.

そしてhttp://msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx

さらに明確にします。

クリティカル セクション オブジェクトは、ミューテックス オブジェクトによって提供されるものと同様の同期を提供しますが、クリティカル セクションは単一プロセスのスレッドによってのみ使用できる点が異なります。

ここで、「遅延構築」が必要でない場合、次のソリューションはモジュール間セーフかつスレッドセーフであり、移植性さえあります。

struct X { };

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

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

X_singleton_helper は、main または DllMain に入る前に正しい値に割り当てられる必要があるため、これも遅延構築されません)。この式では、コンマは演算子であり、句読点ではありません。

ここで明示的に「extern」を使用して、コンパイラが最適化するのを防ぎます (Scott Meyers の記事に関する懸念、最大の敵はオプティマイザです)。また、pc-lint などの静的分析ツールを黙らせます。「main/DllMain の前」は、「効果的な C++ 3rd」項目 4 の「シングル スレッド スタートアップ部分」と呼ばれる Scott meyer です。

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

于 2012-06-21T05:24:21.677 に答える
0

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

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

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

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

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

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

スレッドセーフであると想定するコードの最初の部分で、遅延初期化を行う必要がなくなり、アプリケーションの起動時に「いくらか」多くの処理能力が必要になります。ただし、そうすれば、競合状態に関する多くの頭痛の種が解決されます。

于 2008-10-02T21:01:29.133 に答える
-1

Windows でスレッドセーフな Singleton* 初期化を行う方法は多数あります。実際、それらのいくつかはクロスプラットフォームです。あなたがリンクしたSOスレッドでは、彼らはCで遅延構築されたSingletonを探していました. .

  • 絶対に使ってはいけないもの
于 2008-10-02T20:48:06.293 に答える