66

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

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

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

私が混乱しているのは、スレッドがミューテックスを待機することもできるということです。スレッドがシグナルを送信されたとき、それは単に変数が使用可能になったことを意味します。なぜ条件変数が必要なのですか?

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

4

6 に答える 6

40

説明した方法でそれらを使用できますが、ミューテックスは通知/同期メカニズムとして使用するようには設計されていません。これらは、共有リソースへの相互に排他的なアクセスを提供することを目的としています。ミューテックスを使用して条件を通知するのは厄介で、次のようになります(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. Thread2は、Thread1が「通知後の作業」を終了するまで、「通知前の作業」を続行できません。この設計では、Thread2も必要ありません。つまり、一度に実行できるのは1つだけなので、「前の作業」と「通知後の作業」を同じスレッドに移動してみませんか。
  2. Thread2がThread1をプリエンプトできない場合、Thread1はwhile(1)ループを繰り返すとすぐにミューテックスを再ロックし、通知がなかったとしてもThread1は「通知後の作業」を実行します。これは、Thread1がロックする前にThread2がミューテックスをロックすることを何らかの方法で保証する必要があることを意味します。どうやってそれをしますか?スリープまたはその他のOS固有の手段によってスケジュールイベントを強制する場合がありますが、タイミング、OS、およびスケジューリングアルゴリズムによっては、これでも機能することが保証されていません。

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

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

于 2012-09-24T16:41:03.333 に答える
11

Mutexは共有リソースへの排他的アクセス用であり、条件付き変数は条件が真になるのを待つためのものです。どちらも異なるカーネルリソースです。一部の人々は、ミューテックスを使用して条件変数を自分で実装できると考えるかもしれません。一般的なパターンは「フラグ+ミューテックス」です。

lock(mutex)

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

unlock(mutex)

do_something_on_flag_set();

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

于 2014-04-30T11:28:39.653 に答える
7

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

于 2014-12-29T18:48:30.927 に答える
4

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

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

AdvancedUnixProgrammingAdvancedLinuxProgrammingも読んでください。

PS並列処理とスレッドは、理解するのが難しい概念です。時間をかけて読んで実験し、もう一度読んでください。

于 2012-09-23T10:07:19.193 に答える
3

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

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

  2. 条件付き変数を待つ

  3. プロセス

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

操作のプロデューサースレッドシーケンスは

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

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

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

sema+mutexペアを使用する場合の対応するコンシューマスレッドシーケンスは次のとおりです。

  1. バイナリセマを待つ

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

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

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

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

  6. 手順3の条件チェックがfalseの場合は、手順1に戻ります。

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

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

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

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

ご覧のとおり、条件付き変数を使用する場合のステップ3の無条件処理は、バイナリsemaを使用する場合のステップ3およびステップ4の条件付き処理に置き換えられます。

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

バイナリセマフォは、通常のカウントセマフォに置き換えることができます。これにより、ステップ6からステップ1のループがさらに数回発生する可能性があります。

于 2016-04-17T22:06:39.213 に答える
0

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

http://en.cppreference.com/w/cpp/thread/mutex/unlockで述べられているように、

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

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

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


たとえば、追加のミューテックスを使用して@slowjeljのケースを修正できます(これは誤った修正である可能性があります)。

スレッド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の「所有されていないミューテックスのロック解除」)。

于 2017-05-26T10:02:06.607 に答える