そのため、複数のスレッドが遅延して作成されたシングルトンを初期化しようとするのを防ぐために一般的に使用される C++ の二重チェック ロックが壊れていると主張する多くの記事を見てきました。通常の二重チェック ロック コードは次のようになります。
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
public:
static singleton & instance()
{
static singleton* instance;
if(!instance)
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
}
return *instance;
}
};
問題は明らかにインスタンスを割り当てる行です-コンパイラは自由にオブジェクトを割り当ててからそれにポインタを割り当てるか、またはポインタを割り当てられる場所に設定してから割り当てます。後者のケースはイディオムを壊します -- 1 つのスレッドはメモリを割り当ててポインタを割り当てますが、スリープ状態になる前にシングルトンのコンストラクタを実行しません -- 次に、2 番目のスレッドはインスタンスが null ではないことを確認し、それを返そうとします、まだ構築されていませんが。
の代わりにスレッドローカルブール値を使用してチェックするという提案を見ましたinstance
。このようなもの:
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
static boost::thread_specific_ptr<int> _sync_check;
public:
static singleton & instance()
{
static singleton* instance;
if(!_sync_check.get())
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
// Any non-null value would work, we're really just using it as a
// thread specific bool.
_sync_check = reinterpret_cast<int*>(1);
}
return *instance;
}
};
このようにして、各スレッドはインスタンスが一度作成されたかどうかをチェックしますが、その後停止します。これにより、パフォーマンスが低下しますが、すべての呼び出しをロックするほど悪くはありません。しかし、ローカルな静的 bool を使用した場合はどうなるでしょうか?:
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
public:
static singleton & instance()
{
static bool sync_check = false;
static singleton* instance;
if(!sync_check)
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
sync_check = true;
}
return *instance;
}
};
なぜこれがうまくいかないのですか?sync_check が別のスレッドで割り当てられているときに 1 つのスレッドによって読み取られたとしても、ガベージ値はゼロ以外であり、したがって true になります。この Dr. Dobb の記事では、命令の並べ替えをめぐってコンパイラとの戦いに勝つことはできないため、ロックする必要があると主張しています。これは何らかの理由で機能しないはずだと思いますが、その理由はわかりません。Dr. Dobb の記事にあるように、シーケンス ポイントの要件が失われているとすれば、ロックの後のコードをロックの前に並べ替えることができない理由がわかりません。これにより、C++ マルチスレッドが壊れた期間になります。
ローカル変数であるため、コンパイラが sync_check をロックの前に特別に並べ替えることが許可されていることがわかると思います(静的であっても、参照やポインターを返していません)-しかし、これはまだ解決できます代わりに静的メンバー (事実上グローバル) にすることによって。
それで、これは機能しますか、それとも機能しませんか?なんで?