web-dev-qa-db-ja.com

再帰的ロック(Mutex)対非再帰的ロック(Mutex)

POSIXでは、ミューテックスを再帰的にすることができます。これは、同じスレッドが同じmutexを2回ロックでき、デッドロックしないことを意味します。もちろん、ロックを2回解除する必要もあります。そうしないと、他のスレッドがミューテックスを取得できません。 pthreadをサポートするすべてのシステムが再帰的ミューテックスもサポートしているわけではありませんが、 POSIXに準拠する必要がある場合 にしたい場合は。

他のAPI(より高レベルのAPI)も、通常ロックと呼ばれるmutexを提供します。一部のシステム/言語(Cocoa Objective-Cなど)は、再帰的および非再帰的な両方のミューテックスを提供します。一部の言語は、どちらか一方のみを提供します。例えば。 Javaのmutexは常に再帰的です(同じスレッドが同じオブジェクトで2回「同期」する場合があります)。それらが提供する他のスレッド機能に応じて、再帰的なミューテックスを持たなくても問題はないかもしれません(簡単なミューテックス/条件操作に基づいて再帰的なミューテックスをすでに実装しています)。

私が本当に理解していないこと:非再帰ミューテックスは何に適していますか?同じmutexを2回ロックする場合、スレッドデッドロックが必要なのはなぜですか?それを回避できる高レベル言語(たとえば、これがデッドロックするかどうかをテストし、デッドロックが発生する場合は例外をスローする)でも、通常はそうしません。代わりにスレッドをデッドロックさせます。

これは、誤って2回ロックして1回だけロック解除する場合にのみ、再帰的なミューテックスの場合は問題を見つけるのが難しいので、代わりに間違ったロックが表示される場所をすぐにデッドロックしますか?しかし、ロック解除時にロックカウンターが返され、最後のロックを解除し、カウンターがゼロでないことが確実な場合、例外をスローするか、問題をログに記録することができますか?または、私が見ることができない非再帰的ミューテックスの他の、より有用なユースケースはありますか?または、非再帰的なミューテックスは再帰的なミューテックスよりもわずかに高速になる可能性があるため、単なるパフォーマンスですか?しかし、私はこれをテストしましたが、違いはそれほど大きくありません。

172
Mecki

再帰的ミューテックスと非再帰的ミューテックスの違いは、所有権に関係しています。再帰的なmutexの場合、カーネルは、実際にmutexを最初に取得したスレッドを追跡する必要があるため、代わりにブロックする必要のある別のスレッドと再帰の違いを検出できます。別の答えが指摘したように、このコンテキストを保存するメモリと、それを維持するために必要なサイクルの両方の面で、これの追加のオーバーヘッドの問題があります。

ただし、、他にもここで考慮すべき事項があります。

再帰的なmutexには所有権があるため、mutexを取得するスレッドは、mutexを解放するスレッドと同じでなければなりません。再帰的でないmutexの場合、所有権の感覚はなく、通常、どのスレッドがmutexを使用したかに関係なく、どのスレッドもmutexを解放できます。多くの場合、このタイプの「ミューテックス」は実際にはセマフォアクションであり、ミューテックスを必ずしも除外デバイスとして使用するわけではなく、2つ以上のスレッド間の同期またはシグナリングデバイスとして使用します。

ミューテックスに所有感があるもう1つのプロパティは、優先度継承をサポートする機能です。カーネルはmutexを所有するスレッドとすべてのブロッカーのIDを追跡できるため、優先スレッドシステムでは、現在mutexを所有しているスレッドの優先度を最も優先度の高いスレッドの優先度に上げることが可能になります。現在、mutexでブロックされています。この継承により、このような場合に発生する可能性のある優先順位の逆転の問題が回避されます。 (すべてのシステムがこのようなミューテックスの優先度継承をサポートしているわけではありませんが、所有権の概念によって可能になる別の機能です)。

クラシックVxWorks RTOSカーネルを参照する場合、3つのメカニズムを定義します。

  • mutex-再帰をサポートし、オプションで優先度継承をサポートします。このメカニズムは、通常、データの重要なセクションを一貫した方法で保護するために使用されます。
  • バイナリセマフォ-再帰なし、継承なし、単純な除外なし、テイカーとギバーは同じスレッドである必要はなく、ブロードキャストリリースが利用可能です。このメカニズムは、クリティカルセクションを保護するために使用できますが、スレッド間のコヒーレントシグナリングまたは同期にも特に役立ちます。
  • カウントセマフォ-再帰または継承なし、任意の初期カウントからの一貫したリソースカウンターとして機能します。スレッドは、リソースに対するネットカウントがゼロの場合のみブロックします。

繰り返しになりますが、これはプラットフォームによって多少異なります-特にこれらのことを呼ぶものですが、これは概念とさまざまなメカニズムを代表するものでなければなりません。

143
Tall Jeff

答えはnot効率です。再入不可のミューテックスは、より良いコードにつながります。

例:A :: foo()はロックを取得します。次に、B :: bar()を呼び出します。あなたが書いたとき、これはうまくいきました。しかし、しばらくして誰かがB :: bar()を変更してA :: baz()を呼び出し、これもロックを取得します。

再帰的なミューテックスがない場合、これはデッドロックです。持っている場合は実行されますが、破損する可能性があります。 A :: foo()は、baz()がミューテックスも取得するため実行できなかったという前提で、bar()を呼び出す前にオブジェクトを矛盾した状態にした可能性があります。しかし、おそらく実行すべきではありません! A :: foo()を書いた人は、だれも同時にA :: baz()を呼び出すことができないと仮定しました。それが、これらのメソッドの両方がロックを取得した理由です。

ミューテックスを使用するための正しいメンタルモデル:ミューテックスは不変式を保護します。ミューテックスが保持されている場合、不変式は変化する可能性がありますが、ミューテックスを解放する前に、不変式が再確立されます。再入可能ロックは、2回目にロックを取得したときに不変式が真であると確信できないため、危険です。

再入可能なロックに満足しているのは、これまでこのような問題をデバッグする必要がなかったからです。ところで、最近では、Java [util.concurrent.locksにJavaには再入不可ロックがあります。

118
Jonathan

Dave Butenhof自身が書いたように

「再帰ミューテックスのすべての大きな問題の最大のものは、ロックスキームとスコープを完全に失ってしまうことを奨励していることです。これは致命的です。悪。「スレッドイーター」です。期間。常に、ロックが保持されていることを知らない、または呼び出し先がミューテックスを必要としているかどうかがわからないという理由だけでロックを保持して何かを呼び出している場合、それを保持しすぎています。アプリケーションにショットガンを向けてトリガーを引く。おそらく、並行性を得るためにスレッドの使用を開始したと思われますが、並行性を防止しただけです。」

87
Chris Cleeland

ミューテックスを使用するための正しいメンタルモデル:ミューテックスは不変式を保護します。

これがミューテックスを使用するための本当に正しいメンタルモデルであると確信しているのはなぜですか?適切なモデルはデータを保護しているが、不変条件は保護していないと思います。

不変式を保護する問題は、シングルスレッドアプリケーションにも存在し、マルチスレッドやミューテックスでは一般的ではありません。

さらに、不変式を保護する必要がある場合でも、再帰的ではないバイナリセマフォを使用できます。

14
Corpse

再帰的なmutexが役立つ主な理由の1つは、同じスレッドがメソッドに複数回アクセスする場合です。たとえば、ミューテックスロックが銀行A/cを撤回するよう保護している場合、その撤回にも手数料がかかる場合は、同じミューテックスを使用する必要があります。

4
avis

再帰ミューテックスの唯一の良いユースケースは、オブジェクトに複数のメソッドが含まれている場合です。メソッドのいずれかがオブジェクトのコンテンツを変更するため、状態が再び整合する前にオブジェクトをロックする必要がある場合。

メソッドが他のメソッドを使用する(つまり、addNewArray()がaddNewPoint()を呼び出し、recheckBounds()でファイナライズする)が、それらの関数自体がミューテックスをロックする必要がある場合、再帰ミューテックスはwin-winです。

それ以外の場合(コーディングの問題を解決し、異なるオブジェクトでも使用する)は明らかに間違っています!

3
DarkZeros