18

私は Albahari のスレッドに関する優れた eBook を読んでいて、次のシナリオに出くわしました。

lock (locker)
  lock (locker)
    lock (locker)
    {
       // Do something...
    }

としても

static readonly object _locker = new object();

static void Main()
{
  lock (_locker)
  {
     AnotherMethod();
     // We still have the lock - because locks are reentrant.
  }
}

static void AnotherMethod()
{
  lock (_locker) { Console.WriteLine ("Another method"); }
}

説明から、スレッドは最初の(最も外側の)ロックでブロックされ、外側のロックが終了した後にのみロックが解除されることがわかります。

彼は、「ネストされたロックは、あるメソッドがロック内で別のメソッドを呼び出すときに便利です」と述べています。

なぜこれが役立つのですか?いつこれを行う必要があり、どのような問題を解決しますか?

4

3 に答える 3

11

2 つのパブリック メソッド と がA()ありB()、どちらも同じロックが必要だとします。

A()さらに、呼び出しているとしましょうB()

クライアントはB()直接呼び出すこともできるため、両方のメソッドをロックする必要があります。
したがって、A()が呼び出されると、B()もう一度ロックがかかります。

于 2012-08-17T20:59:53.307 に答える
10

許可されることは有用であるため、そうすることはそれほど有用ではありません。他のパブリックメソッドを呼び出すパブリックメソッドを頻繁に使用する方法を検討してください。パブリックメソッドがロックを呼び出し、パブリックメソッドがそれを呼び出すことで、それが行うことのより広い範囲をロックする必要がある場合、再帰ロックを使用できるということは、そうすることができることを意味します。

2つのロックオブジェクトを使用したいと思う場合もありますが、それらを一緒に使用するため、間違えるとデッドロックの大きなリスクがあります。ロックに与えられているより広い範囲に対処できる場合は、両方の場合に同じオブジェクトを使用し、両方のオブジェクトを使用する場合は繰り返し使用すると、これらの特定のデッドロックが削除されます。

でも...

この有用性については議論の余地があります。

最初のケースでは、ジョー・ダフィーから引用します:

再帰は通常、同期設計が過度に単純化されていることを示しており、コードの信頼性が低下することがよくあります。一部の設計では、機能がロックを取得する機能と、ロックがすでに取得されていると想定する機能に分割されないようにする方法として、ロック再帰を使用しています。これは確かにコードサイズの縮小につながる可能性があり、したがって書き込み時間の短縮につながる可能性がありますが、最終的にはより脆弱な設計になります。非再帰的なロックを取得するパブリックエントリポイントにコードを因数分解し、ロックをアサートする内部ワーカー関数を保持することをお勧めします。再帰的ロック呼び出しは冗長な作業であり、生のパフォーマンスのオーバーヘッドに寄与します。しかし、さらに悪いことに、再帰によっては、プログラムの同期動作、特に不変条件が保持するはずの境界を理解するのが難しくなる可能性があります。通常、ロック取得後の最初の行は、オブジェクトの不変の「セーフポイント」を表していると言いたいのですが、再帰が導入されるとすぐに、このステートメントを自信を持って作成できなくなります。これにより、動的に構成されたときに正しく信頼できる動作を保証することがより困難になります。

(Joeは、彼のブログの他の場所、および並行プログラミングに関する彼の本で、このトピックについてさらに詳しく述べています)。

2番目のケースは、再帰的なロックエントリによってさまざまなタイプのデッドロックが発生する場合、または競合率が非常に高くなり、デッドロックが発生する可能性がある場合とバランスが取れています(この男は、デッドロックをヒットするだけの方がいいと言っています初めて再発したときは、私は同意しません-素晴らしいスタックトレースでアプリをダウンさせた大きな例外をスローするだけの方がいいと思います)。

悪いことの1つは、間違ったタイミングで単純化されることです。コードを記述しているときは、物事をより細かく分割して、いつロックする必要があるかについてより深く考えるよりも、ロック再帰を使用する方が簡単です。ただし、コードをデバッグしている場合、ロックを解除しても、そのロックを解除しても問題が複雑になるわけではありません。なんて悪い方法でしょう。私たちが何をしているのかを知っているとき、その複雑なコードは、時間外に楽しんで、時間に甘んじないように誘惑することであり、私たちがそれを台無しにしたことに気付いたときです。私たちは物事が素晴らしくシンプルであることを最も望んでいます。

あなたは本当にそれらを条件変数と混ぜたくありません。

ねえ、POSIXスレッドはあえてそれらを持っているだけです!

少なくともlockキーワードはMonitor.Exit()、すべてMonitor.Enter()のsに一致するsがない可能性を回避することを意味します。これにより、リスクの一部が発生する可能性が低くなります。そのモデルの外で何かをする必要がある時まで。

最近のロッククラスでは、.NETは、古いコーディングパターンを使用するユーザーをブロックすることなく、ロック再帰の使用を回避するのに少し役立ちます。ReaderWriterLockSlim再帰を使用できるコンストラクターのオーバーロードがありますが、デフォルトはですLockRecursionPolicy.NoRecursion

多くの場合、並行性の問題に対処する際には、より優れた並行性をもたらす可能性があるが、正確性を確認するためにより多くの注意が必要な、より複雑な手法と、より悪い並行性をもたらす可能性があるが、正確さを確認する方が簡単です。ロックを再帰的に使用すると、ロックをより長く保持し、同時実行性が低下し、正確性の確信が薄れ、デバッグが困難になるという手法が得られます。

于 2012-08-17T22:57:24.163 に答える
1

排他制御が必要なリソースがあるが、多くのメソッドがこのリソースに対して動作する場合。メソッドは、ロックされていると想定できない場合があるため、そのメソッド内でロックします。外側のメソッドと内側のメソッドでロックされている場合、本の例と同様の状況が発生します。同じコード ブロックで 2 回ロックする必要がある時間はわかりません。

于 2012-08-17T21:00:56.073 に答える