Meyersによるシングルトンパターンスレッドの実装は安全ですか?


145

Singleton(マイヤーズシングルトン)スレッドの安全な初期化を使用した次の実装はスレッドセーフですか?

static Singleton& instance()
{
     static Singleton s;
     return s;
}

そうでない場合、なぜそしてどのようにスレッドセーフにするのですか?


これがスレッドセーフではない理由を誰かが説明できますか?リンクで言及されている記事は、代替実装を使用したスレッドセーフについて説明しています(ポインター変数、つまり静的シングルトン* pInstanceを使用)。
Ankur、



回答:


168

ではC ++ 11は、スレッドセーフです。よると、標準§6.7 [stmt.dcl] p4

変数が初期化されている間に制御が宣言に同時に入ると、同時実行は初期化の完了を待ちます。

GCCおよびVSによる機能のサポート(Dynamic Initialization and Destruction with Concurrency、別名Magic Statics on MSDN)は次のとおりです。

@Mankarseと@olen_gamのコメントに感謝します。


ではC ++ 03、このコードはスレッドセーフされませんでした。Meyersによる「C ++ and the Perils of Double-Checked Locking」という記事があり、パターンのスレッドセーフな実装について論じています。結論は、多かれ少なかれ、インスタンス化メソッドを完全にロックしている(C ++ 03では)ことです。は、基本的にすべてのプラットフォームで適切な同時実行性を保証する最も簡単な方法ですが、命令がインターリーブされてメモリバリアが戦略的に配置されない限り、ほとんどの形式のダブルチェックロックパターンバリアントは特定のアーキテクチャ競合状態に陥る可能性があります。


3
また、Modern C ++ DesignのAlexandrescuによるシングルトンパターン(ライフタイムとスレッドセーフ)に関する広範なディスカッションもあります。Lokiのサイトを参照してください:loki-lib.sourceforge.net/index.php?n
Matthieu M.

1
boost :: call_onceを使用して、スレッドセーフなシングルトンを作成できます。
CashCow

1
残念ながら、標準のこの部分は、Visual Studio 2012 C ++コンパイラには実装されていません。次の「C ++ 11コア言語機能:同時実行性」の表では「マジックスタティック」と呼ばれています:msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx
olen_garn

標準のスニペットは、構築ではなく破壊に対応しています。標準は、あるスレッドでオブジェクトが破壊されるのを防ぎますか?
stewbasic 2017

IANA(C ++言語)Lですが、セクション3.6.3 [basic.start.term] p2は、オブジェクトが破棄された後でオブジェクトにアクセスしようとすると、未定義の動作にヒットする可能性があることを示唆していますか?
stewbasic 2017

21

スレッドセーフではない理由についての質問に答えるには、の最初のinstance()呼び出しでのコンストラクタを呼び出す必要があるからではありませんSingleton s。スレッドセーフであるためには、これはクリティカルセクションで発生する必要がありますが、クリティカルセクションを取得するという要件はありません(これまでの標準では、スレッドは完全にサイレントです)。コンパイラーは、静的なブール値の単純なチェックと増分を使用してこれを実装することがよくありますが、クリティカルセクションではありません。次の疑似コードのようなもの:

static Singleton& instance()
{
    static bool initialized = false;
    static char s[sizeof( Singleton)];

    if (!initialized) {
        initialized = true;

        new( &s) Singleton(); // call placement new on s to construct it
    }

    return (*(reinterpret_cast<Singleton*>( &s)));
}

だから、これが単純なスレッドセーフなシングルトン(Windows用)だ。これは、WindowsのCRITICAL_SECTIONオブジェクトの単純なクラスラッパーを使用しているため、コンパイラーが自動的に初期化されて、呼び出されるCRITICAL_SECTIONmain()に呼び出されます。理想的には、クリティカルセクションが保持されているときに発生する可能性のある例外を処理できる真のRAIIクリティカルセクションクラスが使用されますが、これはこの回答の範囲を超えています。

基本的な操作は、のインスタンスSingletonが要求されるとロックが取得され、必要に応じてシングルトンが作成され、ロックが解放されてシングルトン参照が返されるというものです。

#include <windows.h>

class CritSection : public CRITICAL_SECTION
{
public:
    CritSection() {
        InitializeCriticalSection( this);
    }

    ~CritSection() {
        DeleteCriticalSection( this);
    }

private:
    // disable copy and assignment of CritSection
    CritSection( CritSection const&);
    CritSection& operator=( CritSection const&);
};


class Singleton
{
public:
    static Singleton& instance();

private:
    // don't allow public construct/destruct
    Singleton();
    ~Singleton();
    // disable copy & assignment
    Singleton( Singleton const&);
    Singleton& operator=( Singleton const&);

    static CritSection instance_lock;
};

CritSection Singleton::instance_lock; // definition for Singleton's lock
                                      //  it's initialized before main() is called


Singleton::Singleton()
{
}


Singleton& Singleton::instance()
{
    // check to see if we need to create the Singleton
    EnterCriticalSection( &instance_lock);
    static Singleton s;
    LeaveCriticalSection( &instance_lock);

    return s;
}

男-それは「より良いグローバルを作る」ためのがらくたです。

この実装の主な欠点(いくつかのバグを回避させなかった場合)は次のとおりです。

  • 場合new Singleton()スロー、ロックが解除されることはありません。これは、ここにある単純なオブジェクトではなく、真のRAIIロックオブジェクトを使用することで修正できます。これは、Boostなどを使用して、プラットフォームに依存しないロック用のラッパーを提供する場合に、移植性を高めるのにも役立ちます。
  • これにより、main()呼び出し後にシングルトンインスタンスが要求されたときにスレッドの安全性が保証されます。その前に(静的オブジェクトの初期化のように)呼び出すと、初期化されていCRITICAL_SECTIONない可能性があるため、機能しない可能性があります。
  • インスタンスが要求されるたびにロックを取得する必要があります。私が言ったように、これは単純なスレッドセーフな実装です。より良いものが必要な場合(または、ダブルチェックロックテクニックなどに欠陥がある理由を知りたい場合)は、Grooの回答にリンクされている文書を参照してください。

1
ええとああ。場合はどうなるのnew Singleton()スロー?
sbi 2009年

@ボブ-公平を期すために、適切なライブラリのセットを使用すると、コピー不可性と適切なRAIIロックに関係するすべての残骸がなくなるか、最小限になります。しかし、私はこの例を合理的に自己完結させたいと思っていました。シングルトンはたぶん最小限の利益のために多くの作業ですが、グローバルの使用を管理する上でそれらが有用であることがわかりました。命名規則だけではなく、どこでいつ使用されるかを簡単に見つけられる傾向があります。
マイケル・バー、

@sbi:この例では、new Singleton()スローした場合、ロックに間違いなく問題があります。lock_guardBoostのような適切なRAIIロッククラスを使用する必要があります。私はこの例を多かれ少なかれ自己完結型にしたかったので、それはすでに少し怪物のようだったので、例外的な安全性を省略しました(ただし、それは呼び出されました)。多分私はそれを修正して、このコードが不適切な場所でカットアンドペーストされないようにする必要があります。
マイケル・バー、

なぜシングルトンを動的に割り当てるのですか?なぜ 'pInstance'を 'Singleton :: instance()'の静的メンバーにしないのですか?
マーティンヨーク

@Martin-完了。そうです、それにより少し単純になります-私がRAIIロッククラスを使用した場合はさらに良いでしょう。
マイケルバー

10

次の標準(セクション6.7.4)を見ると、静的ローカル初期化がどのようにスレッドセーフであるかがわかります。したがって、標準のそのセクションが広く実装されると、Meyerのシングルトンが推奨される実装になります。

私はすでに多くの答えに同意しません。ほとんどのコンパイラはすでにこの方法で静的初期化を実装しています。注目すべき例外の1つはMicrosoft Visual Studioです。


6

正しい答えはコンパイラによって異なります。スレッドセーフにすることができます。「自然に」スレッドセーフではありません。


5

次の実装はスレッドセーフですか?

ほとんどのプラットフォームでは、これはスレッドセーフではありません。(通常の免責事項を追加して、C ++標準ではスレッドが認識されないため、合法的には、それがスレッドであるかどうかは明記されていません。)

そうでない場合、なぜ[...]?

そうではない理由は、複数のスレッドがsコンストラクターを同時に実行することを妨げるものがないためです。

スレッドセーフにする方法は?

Scott MeyersとAndrei Alexandrescuによる"C ++ and the Perils of Double-checked Locking"は、スレッドセーフなシングルトンに関するかなり優れた論文です。


2

MSaltersが言ったように:使用するC ++実装によって異なります。ドキュメントを確認してください。他の質問については、「そうでなければ、なぜですか?」-C ++標準では、スレッドについてはまだ何も言及されていません。しかし、次のC ++バージョンはスレッドを認識し、静的ローカルの初期化はスレッドセーフであると明示的に述べています。2つのスレッドがそのような関数を呼び出す場合、1つのスレッドが初期化を実行し、もう1つのスレッドはブロックして、終了するまで待機します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.