2012-01-30 11 views
0

マルチスレッドプログラムでシングルトンパターンを使用したいと思います。ダブルチェックされたロック方法は、その効率には適しているようですが、この方法は壊れており、簡単にはできません。ダブルチェックロックの有効な代替方法ですか?

私は、ダブルチェックロックの代替として動作することを期待して、次のコードを記述します。それはスレッドセーフなシングルトンパターンの正しい実装ですか?

static bool created = false; 
static Instance *instance = 0; 

Instance *GetInstance() { 
    if (!created) { 
     Lock lock; // acquire a lock, parameters are omitted for simplicity 
     if (!instance) { 
      instance = new Instance; 
     } else { 
      created = true; 
     } 
    } 
    return instance; 
} 

最初の呼び出しでインスタンスが作成されます。 2番目の呼び出しはtrueに作成されます。最後に、他のすべての呼び出しは、よく初期化されたインスタンスを返します。

http://voofie.com/content/192/alternative-to-double-checked-locking-and-the-singleton-pattern/

+5

単純に 'Instance * GetInstance(){静的インスタンスインスタンス;リターン&インスタンス; } ' – kennytm

+4

誰かがあなたにまだシングルトンが悪いと言ったことはありますか?スレッドの安全性を象徴するものを作成するだけの複雑さは、問題の一部に過ぎません。 – cHao

+0

この実装だけでなく、シングルトン原理全体が壊れています;) –

答えて

3

いいえ、これは役に立ちません。 createdinstanceへの書き込みが非アトミックな場合、その値がmutexをロックしないスレッドに見えるという保証はありません。

スレッド1はgetInstanceを呼び出します。 createdfalseであり、instanceはnullなので、mutexをロックして新しいインスタンスを作成します。スレッド1はgetInstanceを再度呼び出し、今回はcreatedtrueに設定します。スレッド2は今すぐgetInstanceを呼び出します。プロセッサのメモリ管理のばらつきによって、createdtrueと表示されますが、instanceがnullでないと保証されていなくても、指し示されたインスタンスのメモリ値が一貫しているという保証はありません。

アトミックを使用していない場合は、mutexを使用する必要があり、保護された変数へのすべてのアクセスにそれらを使用する必要があります。

追加情報:コンパイラとランタイムが協調して、あるスレッドがmutexロックを解放し、別のスレッドが同じmutexのロックを取得した場合、2番目のスレッドは完了したすべての書き込みを確認できます最初に。これは非原子アクセスでは当てはまりませんし、コンパイラとランタイムがあなたに保証するメモリの順序制約(C++ 11アトミックでは順序制約を選択できます)に応じて、アトミックアクセスの場合もそうでない場合もあります。

+0

私はあなたの答えに関して私の質問を更新しました。ありがとうございました。 – Ross

+0

追加情報で回答を更新しました。 –

+0

私はロックを使用しました(ロックロック;)。それは同じではありませんか? – Ross

3

これは、ダブルチェックロックの同じ信頼性を持っています。 "トリプルチェック"、さらには "4重チェック"でさらに多くを得ることができますが、完全な信頼性は不可能であることが実証できます。

ローカル静的変数を宣言すると、コンパイラは同じロジックを実装することに注意してください。

#include<memory> 
#include "Instance.h" //or whatever... 

Instance* GetInstance() 
{ 
    static std::unique_ptr<Instance> p(new Instance); 
    return p.get(); 
} 

コンパイラは、マルチスレッド環境用に設定されている場合は、ミューテックスで静的Pを保護sould、非常に最初の呼び出しでのpを初期化するときにロックを管理します。また、 "at_exit"チェーンの末尾にチェーンを破棄して、プログラム終了時の適切な破壊が実行されるようにする必要があります。

[編集] これはC++ 11の要件であり、一部のC++ 03の事前標準でしか実装されていないため、コンパイラの実装と設定を確認してください。

今のところ、私はMinGW 4.6をオンにしてVS2010を確実にすることができます。

+3

これはC++ 11コンパイラでは必要ですが、現在のコンパイラのすべてがマルチスレッド環境でローカルの 'static'オブジェクトに必要な同期を提供するわけではありません。 –

+0

あなたが慎重に見ているかどうかは二重チェックされていません。これはダブルロックで、異なるスレッドを少なくとも2回はロックする必要があり、作成されたインスタンスとインスタンスの割り当てに順序を保証します。ちょうどそれが正しいとは言いませんが役に立たない。あなたが無効だと思う状況はありますか? – Ross

+0

@AnthonyWilliams:ありがとう:ただ編集しました –

1

このコードには、createdが別のスレッドによって同時に書き込まれている間に読み取られるという競合条件が含まれています。

結果として、定義されていない動作があり、そのコードを書き込む有効な方法ではありません。

KennyTMはコメントで指摘したように、はるかに良い選択肢がある:

Instance* GetInstance() { static Instance instance; return &instance; } 
+0

インスタンスは高価なオブジェクトであり、使用されない可能性があります。だから、怠惰な初期化が必要です。 – Ross

+0

@Ross:これは怠惰な初期化です - 関数スコープの静的な統計は、プログラムの実行が定義を「交差」するときに初期化されます。このコードは、問題を解決するための実装に渡します。これは、実装が既にスレッドセーフなローカル統計を実装している場合に最適です。そうでなければそれほど素晴らしいわけではありません。 –

2

いいえ、あなたのコードとダブル ロックを確認の間に違いは絶対にありません。 正しい実装は次のとおりです。

static std::mutex m; 

Singleton& 
Singleton::instance() 
{ 
    static Singleton* theOneAndOnly; 
    std::lock_guard l(m); 
    if (theOneAndOnly == NULL) 
     theOneAndOnly = new Singleton; 
    return *theOneAndOnly; 
} 

これが問題を引き起こす場合を想像するのは難しい、それは保証 です。あなたは毎回ロックを取得しますが、 未定義のミューテックスを取得することはかなり安くなければなりません。 シングルトンはそれほどではありませんし、タイトなループの途中で にアクセスしなければならないループに入る前に リファレンスを取得してから使用を停止し、それを使用してください。

0

解決策は正常に動作しますが、もう一度チェックします。それはロックを毎回取得しないため

右ダブルチェックロックのように見える、

static Instance *instance = 0; 

Instance *GetInstance() { 
    if (instance == NULL) //first check. 
    { 
     Lock lock; //scope lock. 
     if (instance == NULL) //second check, the second check must under the lock. 
      instance = new Instance; 
    } 

    return instance; 
} 

ダブルチェックロックは、良好な性能を持っています。スレッドセーフであれば、ロックは1つのインスタンスしか作成されません。

+0

http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdfを読んで、なぜ壊れているのかを確認してください。 – Ross

関連する問題