Singleton
(マイヤーズシングルトン)スレッドの安全な初期化を使用した次の実装はスレッドセーフですか?
static Singleton& instance()
{
static Singleton s;
return s;
}
そうでない場合、なぜそしてどのようにスレッドセーフにするのですか?
Singleton
(マイヤーズシングルトン)スレッドの安全な初期化を使用した次の実装はスレッドセーフですか?
static Singleton& instance()
{
static Singleton s;
return s;
}
そうでない場合、なぜそしてどのようにスレッドセーフにするのですか?
回答:
では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では)ことです。は、基本的にすべてのプラットフォームで適切な同時実行性を保証する最も簡単な方法ですが、命令がインターリーブされてメモリバリアが戦略的に配置されない限り、ほとんどの形式のダブルチェックロックパターンバリアントは特定のアーキテクチャで競合状態に陥る可能性があります。
スレッドセーフではない理由についての質問に答えるには、の最初の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_SECTION
前main()
に呼び出されます。理想的には、クリティカルセクションが保持されているときに発生する可能性のある例外を処理できる真の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
ない可能性があるため、機能しない可能性があります。new Singleton()
スロー?
new Singleton()
スローした場合、ロックに間違いなく問題があります。lock_guard
Boostのような適切なRAIIロッククラスを使用する必要があります。私はこの例を多かれ少なかれ自己完結型にしたかったので、それはすでに少し怪物のようだったので、例外的な安全性を省略しました(ただし、それは呼び出されました)。多分私はそれを修正して、このコードが不適切な場所でカットアンドペーストされないようにする必要があります。
次の実装はスレッドセーフですか?
ほとんどのプラットフォームでは、これはスレッドセーフではありません。(通常の免責事項を追加して、C ++標準ではスレッドが認識されないため、合法的には、それがスレッドであるかどうかは明記されていません。)
そうでない場合、なぜ[...]?
そうではない理由は、複数のスレッドがs
コンストラクターを同時に実行することを妨げるものがないためです。
スレッドセーフにする方法は?
Scott MeyersとAndrei Alexandrescuによる"C ++ and the Perils of Double-checked Locking"は、スレッドセーフなシングルトンに関するかなり優れた論文です。