二重のチェックをロックするために、この修正プログラムで何が悪いのでしょうか?
-
09-09-2019 - |
質問
私は今、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でないことを確認し、それを返すようにしようとします、それはまだ構築されていないにもかかわらずます。
I スレッドを使用するように提案を見ました地元ブール、代わりに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;
}
};
インスタンスは、一度作成されている場合は、この方法は、各スレッドは、チェックアップ終了ではなく、ほとんどが悪いすべてのコールをロックするなど、まだいくつかのパフォーマンスヒットを伴うが、これは、その後停止します。しかし、我々はちょうど地元の静的なブール値を使用したものとします。?
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は、一つのスレッドによって読み取られるようにしたとしても、それは他に割り当てられていたときにガベージ値がまだゼロでないため、真となります。あなたはとの戦いに勝つことは決してないだろうので、あなたがロックしていることを主張する この博士ドッブの記事並べ替え命令を超えるコンパイラ。これは私は、これが何らかの理由で動作してはならないと思わせるが、私は理由を理解することはできません。博士ドッブの記事は、私は信じています失うほどの理由は、のシーケンスポイントの要件は、私は理解していないしている場合は、ロック後の任意ののコードは、ロックの前にあることを並べ替えることができませんでした。 C ++マルチスレッド壊れた期間を作ることになる。
私は(と、それは静的だにもかかわらず、我々はそれへの参照やポインタを返していない)私は、コンパイラは、具体的には、ローカル変数ですので、ロック前にあることをsync_checkを並べ替えることが許可されて見ることができると思います - しかし、これは可能性まだ代わりに静的メンバ(有効グローバル)ことによって解決される。
だから、この作品はなりますか、それはないだろうか?なぜ?
解決
あなたの修正は何も解決しません。例として、インスタンスへの最初の二つの呼び出しは、2つの異なるCPU上でほぼ同時に起こる想像します。最初のスレッドは、ロックを獲得そのために、真のポインタと設定sync_checkを初期化するが、プロセッサは、メモリへの書き込みの順序を変更することができるであろう。他のCPUで、それはそれが真実であることがわかり、sync_checkをチェックするために、第2のスレッドは可能ですが、インスタンスがまだメモリに書き込まれないことがあります。参照してください。ロックレスプログラミングの考慮事項>詳細ます。
あなたが言及スレッド固有sync_checkソリューションは、(あなたが0にポインタを初期化すると仮定した場合)、その後動作するはずです。
他のヒント
(それは.NET / C#指向のだが)、これについていくつかの素晴らしい読書がここにあります:<のhref = "http://msdn.microsoft.com/en-us/magazine/cc163715.aspx" のrel = "nofollowをnoreferrer 「> http://msdn.microsoft.com/en-us/magazine/cc163715.aspx の
何それはつまるところすると、それはロジックと思った場合、それはあなたが/読み込み(これまでのオリジナルのPentium以降、CPUは特定の命令を並べ替えることができ、この変数にアクセスするために書き込みを並べ替えることができないCPUを伝えることができる必要があるということです)影響を受けない、そしてそれはキャッシュが一貫していることを保証する必要があるということは(それを忘れないでください - 私たちは、開発者は、すべてのメモリがちょうど1フラット資源であるが、実際には、各CPUコアがキャッシュを持っていることをふりをしてもらう、いくつかの(L1)非共有、いくつかは)時々(L2)で共有される可能性があります - あなたのinitizlizationは、メインRAMに書き込むかもしれないが、他のコアがキャッシュに初期化されていない値があるかもしれません。あなたが任意の同時実行のセマンティクスを持っていない場合、CPUは、キャッシュが汚れているということを知らないかもしれない。
私はC ++側を知らないが、ネットでは、あなたはそれへのアクセスを保護するために揮発性として変数を指定します(またはあなたがSystem.Threadingでのメモリの読み出し/書き込みバリアメソッドを使用します)。
余談として、私は、.NET 2.0で、二重にチェックロックは(そこに任意の.NET読者のために)、「揮発性」変数なしで動作することが保証されていることを読んだ - あなたのC ++コードのお手伝いをしていないことます。
あなたが安全になりたい場合は、C#で揮発性として変数をマーキングのC ++と同等の操作を行う必要があります。
「後者の場合は、イディオムを壊す - 。2つのスレッドがシングルトンを作成するに終わるかもしれない」
しかし、私は正しくコード、最初の例を理解していれば、インスタンスがすでに存在する場合、それは一つのスレッドの取得者がそれをロックしない場合は、(同時に複数のスレッドによって実行される可能性があります)をチェックして、インスタンスを作成します - 一つだけのスレッドがその時点で作成を実行することができます。他のすべてのスレッドがロックアウトされたとお待ちしております。
インスタンスが作成され、ミューテックスは、次の待機中のスレッドがミューテックスをロックしますが、それはチェックが失敗するため、新しいインスタンスを作成しようとはしませんロック解除されたらます。
次回は、インスタンス変数がチェックされている何のスレッドが新しいインスタンスを作成しようとしませんように設定されます。
私は別のスレッドが同じ変数をチェックしながら、一つのスレッドがインスタンスに新しいインスタンスのポインタを割り当てている場合についてはよく分からない - 私はそれがこのケースで正しく処理されると信じています。
。私はここで何かが足りないのですか?
操作の順序変更についてが、この場合には、それはので、私はそれが起こることを期待していないロジックを変更することになるかわからない[OK]を - 。しかし、私はこのトピックには専門家だ。