web-dev-qa-db-ja.com

条件変数が必要な場合、ミューテックスでは十分ではありませんか?

条件変数の概念が存在する理由は、mutexだけでは不十分だと確信しています。しかし、それは私を打ち負かし、条件変数が不可欠であるとき、私は具体的なシナリオで自分を納得させることができません。

条件変数、ミューテックス、ロックの違い 質問の受け入れられた答えは、条件変数が

「シグナリング」メカニズムでロックします。リソースが利用可能になるのをスレッドが待機する必要がある場合に使用されます。スレッドはCVで「待機」でき、リソースプロデューサーは変数に「シグナル」を送信できます。その場合、CVを待機するスレッドは通知を受け、実行を継続できます

私が混乱しているのは、スレッドもミューテックスを待つことができ、それが通知されると、単に変数が利用可能になったことを意味しますが、なぜ条件変数が必要なのですか?

追伸:また、とにかく条件変数を保護するためには、条件変数の目的を見ないように私のビジョンをより斜めにするためにミューテックスが必要です。

45
legends2k

説明どおりに使用できますが、ミューテックスは通知/同期メカニズムとして使用するようには設計されていません。共有リソースへの相互排他的アクセスを提供することを目的としています。ミューテックスを使用して条件を通知するのは厄介で、次のようになります(Thread1はThread2によって通知されます)。

スレッド1:

while(1) {
    lock(mutex); // Blocks waiting for notification from Thread2
    ... // do work after notification is received
    unlock(mutex); // Tells Thread2 we are done
}

スレッド2:

while(1) {
    ... // do the work that precedes notification
    unlock(mutex); // unblocks Thread1
    lock(mutex); // lock the mutex so Thread1 will block again
}

これにはいくつかの問題があります。

  1. Thread1が「通知後の作業」で終了するまで、Thread2は「通知に先行する作業を続行」できません。この設計では、Thread2は不要です。つまり、特定の時間に実行できるのは1つだけなので、「先行する作業」と「通知後の作業」を同じスレッドに移動しないでください。
  2. Thread2がThread1を横取りできない場合、Thread1はwhile(1)ループを繰り返すとすぐにmutexを再ロックし、Thread1は通知がなくても「通知後の作業」を実行します。つまり、Thread1がロックする前に、Thread2がmutexをロックすることを何らかの形で保証する必要があります。どうやって?スリープまたはその他のOS固有の手段によってスケジュールイベントを強制することもできますが、タイミング、OS、およびスケジューリングアルゴリズムによっては、これが機能することは保証されていません。

これらの2つの問題は軽微なものではなく、実際には、設計上の大きな欠陥であり、潜在的なバグでもあります。これらの両方の問題の原因は、ミューテックスが同じスレッド内でロックおよびロック解除されるという要件です。それでは、上記の問題をどのように回避しますか?条件変数を使用してください!

ところで、同期のニーズが本当に単純な場合は、条件変数の追加の複雑さを回避する単純な古いセマフォを使用できます。

28
slowjelj

ミューテックスは共有リソースの排他的アクセス用であり、条件変数は条件が真になるのを待つためのものです。人々は、カーネルのサポートなしで条件変数を実装できると考えるかもしれません。考えられる一般的な解決策は、「フラグ+ミューテックス」のようなものです:

lock(mutex)

while (!flag) {
    sleep(100);
}

unlock(mutex)

do_something_on_flag_set();

ただし、待機中にミューテックスをリリースすることはないため、スレッドセーフな方法でフラグを設定することはできません。これが条件変数を必要とする理由です。条件変数で待機している場合、関連するミューテックスは、シグナルが送信されるまでスレッドによって保持されません。

7
Dagang

私もこれについて考えていましたが、どこでも見逃していた最も重要な情報は、mutexは一度に1つのスレッドしか所有(および変更)できないということでした。したがって、1つのプロデューサーと複数のコンシューマーがある場合、プロデューサーはミューテックスの生成を待機する必要があります。条件付き彼はいつでも生成できる変数。

5
MBI

条件付きvarとmutexのペアは、バイナリセマフォとmutexのペアで置き換えることができます。条件付きvar + mutexを使用する場合のコンシューマスレッドの操作のシーケンスは次のとおりです。

  1. ミューテックスをロックする

  2. 条件付き変数を待ちます

  3. 処理する

  4. ミューテックスのロックを解除します

プロデューサースレッドオペレーションのシーケンスは

  1. ミューテックスをロックする

  2. 条件付き変数を通知する

  3. ミューテックスのロックを解除します

Sema + mutexペアを使用する場合の対応するコンシューマスレッドシーケンスは

  1. バイナリセマを待ちます

  2. ミューテックスをロックする

  3. 予想される状態を確認する

  4. 条件が真の場合、処理します。

  5. ミューテックスのロックを解除します

  6. ステップ3の条件チェックがfalseだった場合、ステップ1に戻ります。

プロデューサースレッドのシーケンスは次のとおりです。

  1. ミューテックスをロックする

  2. バイナリセマを投稿する

  3. ミューテックスのロックを解除します

条件変数を使用する場合のステップ3の無条件処理を見ることができるように、バイナリセマを使用する場合、ステップ3およびステップ4の条件処理に置き換えられます。

理由は、競合状態でsema + mutexを使用する場合、別のコンシューマスレッドがステップ1と2の間に忍び込んで、条件を処理/無効化する可能性があるためです。条件付き変数を使用する場合、これは起こりません。条件付き変数を使用する場合、ステップ2の後、条件は真であることが保証されます。

バイナリセマフォは、通常のカウントセマフォに置き換えることができます。これにより、手順6から手順1のループがさらに数回繰り返される場合があります。

3
Frank M

あるスレッドから別のスレッドへの状態(条件)の変化を通知するために、mutex(各cond.var。はmutexに属します)と共に使用される条件変数が必要です。アイデアは、ある条件が真になるまでスレッドが待機できるということです。このような条件はプログラム固有です(つまり、「キューが空」、「マトリックスが大きい」、「リソースがほとんど使い果たされている」、「計算ステップが終了している」など)。ミューテックスには、いくつかの関連する条件変数がある場合があります。また、条件変数は、「mutexがロックされている」と単純に表現できない場合があるため、条件変数が必要です(したがって、条件の変更を他のスレッドにブロードキャストする必要があります)。

いくつかの良いposixスレッドチュートリアルを読んでください。 このチュートリアル または that または that one。さらに良いことに、良いpthreadの本を読んでください。 この質問 を参照してください。

Advanced Unix Programming および Advanced Linux Programming も読んでください

追伸並列処理とスレッドは把握が難しい概念です。読んで実験し、もう一度読んでください。

実装定義だと思います。
ミューテックスが十分かどうかは、ミューテックスをクリティカルセクションのメカニズムと見なすか、それともそれ以上のものと見なすかによって異なります。

http://en.cppreference.com/w/cpp/thread/mutex/unlock で述べたように、

ミューテックスは現在の実行スレッドによってロックされている必要があります。ロックされていない場合、動作は未定義です。

つまり、スレッドは、C++でそれ自体がロック/所有しているミューテックスのみをロック解除できます。
しかし、他のプログラミング言語では、プロセス間でミューテックスを共有できる場合があります。

したがって、2つの概念を区別することは、パフォーマンスの考慮事項にすぎない場合があります。複雑な所有権の識別やプロセス間共有は、単純なアプリケーションには価値がありません。


たとえば、@ slowjeljのケースを追加のmutexで修正できます(間違った修正である可能性があります)。

スレッド1:

lock(mutex0);
while(1) {
    lock(mutex0); // Blocks waiting for notification from Thread2
    ... // do work after notification is received
    unlock(mutex1); // Tells Thread2 we are done
}

スレッド2:

while(1) {
    lock(mutex1); // lock the mutex so Thread1 will block again
    ... // do the work that precedes notification
    unlock(mutex0); // unblocks Thread1
}

しかし、プログラムは、コンパイラによって残されたアサーションをトリガーしたことを訴えます(例:Visual Studio 2015の「所有されていないmutexのロック解除」)。

0
Mr. Ree