質問

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

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

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

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

役に立ちましたか?

解決

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

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

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

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

古典的なVxWorks RTOSカーネルを参照する場合、3つのメカニズムを定義します:

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

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

他のヒント

答えは効率ではありません 。再入不可のミューテックスはコードの改善につながります。

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

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

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

リエントラントロックに満足しているのは、以前にこのような問題をデバッグする必要がなかったからです。ちなみに、Javaは最近java.util.concurrent.locksに再入不可ロックを持っています。

Dave Butenhof自身が書いたとおり

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

  

使用に適したメンタルモデル   ミューテックス:ミューテックスは   不変。

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

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

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

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

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

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

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top