2011-01-25 15 views
4

私はここでこの古いBoost Thread FAQを読んでいます。boost::mutexコピー不可オブジェクトをメンバーとするクラスのコピー作成と代入演算子を実装するガイドラインがあります。コピーできないクラスの代入演算子boost :: mutex

私はコピーコンストラクタでうまくいますが、私は代入演算子に疑念があります。 以下の説明はまだ有効ですか?

// old boost thread  
    const counter & operator=(const counter& other){ 
    if (this == &other) 
     return *this; 

    boost::mutex::scoped_lock lock1(&m_mutex < &other.m_mutex ? 
             m_mutex : other.m_mutex); 
    boost::mutex::scoped_lock lock2(&m_mutex > &other.m_mutex ? 
             m_mutex : other.m_mutex); 
    m_value = other.m_value; 

    return *this; 
} 

が、これはにアップデートしてはいけません。

// new boost thread  
const counter& operator=(const counter& other){ 
    if (this == &other) 
     return *this; 

    boost::unique_lock<boost::mutex> l1(m_mutex, boost::defer_lock); 
    boost::unique_lock<boost::mutex> l2(other.m_mutex, boost::defer_lock); 
    boost::lock(l1,l2); 
    m_value = other.m_value; 

    return *this; 
} 
+1

私はちょうど、技術的には、FAQで与えられた解決策が必ずしも機能しないことを指摘したいと思います。そのようなポインタを比較することは明記されておらず、*両方の*比較に対して 'true'または' false'を返す可能性があります。適切な方法は、 'void *'や 'boost :: mutex *'に 'std :: less'(とkin)の特殊化を使うことです。なぜなら、関数比較のファンクタは全てのポインタの特殊化に対して全順序付けを行うからです。 (まだ*特に*の結果は保証されていませんが、有効な注文が保証されています) – GManNickG

答えて

5

まず第一に、私は質問が複数の任意のミューテックスをロックすると、デッドロックを回避することについてであると仮定します。重要なことは、一連のミューテックスを使用して、コード全体で常に同じオーダリング規則を使用することです。 mutex Aが常にBの前にロックされることを保証できれば、Bは常にCの前にロックされ、Aは常にCの前にロックされるので、デッドロックを避けることができます。

最初のコード例では、まず下部メモリアドレスでミューテックスをロックします。アドレスの順序付けが不変であるため、これは正常に動作します。 2番目のバージョンはデッドロックを回避するofficial Boost methodです。このドキュメントでは、内部的にどのような順序付けが行われるかについては記述されていません。この情報をソースで探し、コード内の他の場所でこの情報を使用することはお勧めしません。ライブラリが変更された場合、微妙なことが起こる可能性があります。

あなたがコードを作成する前に一度に複数のミューテックスを保持していないのであれば、Boostの方法を使用することは間違いありません。正確な順序付けを心配する必要はありませんあなたはそれを毎回後押しするのです。残りのコードでメモリの順序が使用されている場合は、にする必要があります。あなたのコードが他の規約を完全に使用している場合は、ここでもそれを適用する必要があります。いかなる状況下でも、同時に開催される可能性のあるロックのセット内でコンベンションを混ぜ合わせるべきではありません。それは単に問題を求めているだけです。

コメントに質問に答えるために:カスタムロック発注方式は、あなたが長い時間のために、いくつかのロック(A)を保持する必要がある場合は特に、特定の状況で役立ちます

を、いくつかの(B )短く、は長いものを保持します。例えば、Aのオブジェクトに長時間のジョブを実行する必要がある場合、Bの多くのインスタンスに一時的に影響します。規則は、まずAのロックを取得することです。次に Bオブジェクトのロック

 
void doStuff(A& a, std::list<B*> bs) 
{ 
    boost::unique_lock<boost::mutex> la(a.mutex); // lock a throughout 
    for (std::list<B*>::iterator ib = bs.begin(); ib != bs.end(); ++ib) 
    { 
    // lock each B only for one loop iteration 
    boost::unique_lock<boost::mutex> lb(ib->mutex); 
    // work on a and *ib 
    // ... 
    } 
} 

あなたは各ループの繰り返し間のロックを放棄し、ブーストの/ C++ 0xののロック順序を使用することができるかもしれませんが、どのようなdoStuff()に依存して、そのアルゴリズムが複雑か混乱するかもしれないん。

別の例:オブジェクトが(ガベージコレクションのコピーなどの理由で)必ずしも同じメモリロケーションに留まらないランタイム環境では、オーダーのメモリアドレスに依存することは信頼できません。したがって、各オブジェクトに一意のIDを与え、ID順序でロックの順序付けを行うことができます。

+0

正解。デッドロックには反対です。 boost :: lockやmemory ordering以外の他の「順序付け規則」やスキームを理解することはできません。私が見ることができる例がありますか?サイドノート:これはboostメソッドだけでなく、std :: lock(l1、l2)を使ったC++ 0xメソッドです。私はあなたに同意します。それに詳しいことはしません。 –

+0

カスタムロック注文方式は、特にロック(A)を長時間保持する必要がある場合に便利ですが、長いものを保持している間は一時的に(B)短くしか保持しない場合に便利です。たとえば、タイプAのオブジェクトに対して長いジョブを実行する必要がある場合、Bのインスタンスの多くに一時的に影響を与えます。規則は、Bオブジェクトの最初の*、* *ロックに対して常にロックを取得することです。 – pmdj

+0

例カスタムオーダーコンベンションではコメントが長すぎるため、答えに追加しました。 – pmdj

関連する問題