1

Monitor.Enter()によって排他的にロックされたオブジェクトの参照を変更するとどうなるかを確認したかったのです。予想どおり、SynchronizationLockExceptionがスローされました。しかし、例外がスローされる前に、いくつかのスレッドがモニターを通過するのを見て驚いた。

以下のコードが実行していることは次のとおりです。

  1. 100スレッドを作成して開始します
  2. ManualResetEventが設定されるまで、すべてのスレッドを待機させます。
  3. ManualResetEventを設定します-インディレースで緑の旗を振るようなものです。
  4. xに排他ロック(Monitor.Enter(x))を設定します
  5. xの参照を変更します。

この時点で、ある種の例外がスローされることを期待していましたが、それはMonitor.Exit(x)まで発生しません。本当に奇妙なのは、例外が発生する前に10〜20個のスレッドがロックを通過できるように見えることです。それはどのように起こりますか?すべきではないようです。もちろん、排他的にロックされたオブジェクトの参照を変更することはできません。実際のコードでそれを行うことは決してありませんが、それでも他のスレッドがモニターを通過するのを見て驚いた。あなたの考え?

using System;
using System.Threading;

namespace ThreadingPlayground
{
  class Program
  {
    private int _value;
    object x = new object();

    ManualResetEvent _event = new ManualResetEvent(false);

    static void Main()
    {
      Program p = new Program();
      p.StartThreads();
      Console.ReadLine();
    }

    private void StartThreads()
    {

      for(int i = 0;i<100;i++)
      {
        Thread t = new Thread(IncrementValue);
        t.Start();
      }
      _event.Set();
    }

    private void IncrementValue()
    {
      WaitHandle.WaitAll(new WaitHandle[] {_event});

      Monitor.Enter(x);

      // Change the reference of the exclusively locked object. This 
      // allows other threads to enter, should this be possible?
      x = Thread.CurrentThread.ManagedThreadId.ToString();

      Console.WriteLine(++_value);

      // throws a SynchronizationLockException 
      // but not until 10 - 20 more lines are written
      Monitor.Exit(x);
    }
  }
}

コンソール出力、いくつかのスレッドがモニターを通過したように見えますか?

4

3 に答える 3

4

あなたが見ているのは期待される振る舞いです。に参照を渡すために使用される実際の変数については、特別なことは何もありませんMonitor.Enter()。変数には新しい値があり、その参照はどこにもロックされていないため、参照を変更しても、他のスレッドが排他ロックを取得するのを妨げることはありません。

Exit渡される参照に対してスレッド呼び出しExitには排他ロックがないため、問題が発生します。別のスレッドにはロックがある可能性がありますが、実行しているスレッドにはロックがありません。

ご存知のように、これが、参照が変更されない変数を使用してロックを行うことが常に最善である理由です。リソースの変数が変更される可能性がある場合は、新しい参照を使用してください。

これで十分ですか?

于 2009-05-06T04:30:00.110 に答える
2

'x'はオブジェクトへの参照です。オブジェクト自体ではありません。あなたがするとき

      x = Thread.CurrentThread.ManagedThreadId.ToString();

xが以前に参照していたロックされたオブジェクトを破棄し、xに他のオブジェクトを参照させました。今あなたがするとき

      Monitor.Exit(x);

このオブジェクトは実際にはロックされていないため、例外が発生します。ロックしたオブジェクトはガベージコレクターによって収集されるガベージになります。

于 2009-05-06T04:33:44.530 に答える
1

この動作の理由は、ここで x の値を変更しているためです。

x = Thread.CurrentThread.ManagedThreadId.ToString();

だからあなたがたどり着いたとき

Monitor.Exit(x)

別のオブジェクトでロックを解除しています。1 つのキーで南京錠を置き、別の南京錠からキーで南京錠を外そうとするようなものです。

さらに、Console.Writeline は他の命令に比べてコストのかかる命令であるため、いくつかのスレッドがモニターに入り、そのうちの 1 つがフィニッシュ ラインに近づくことになります。

実行例を次に示します。

たくさんのスレッドを開始します。

  • ThreadT1は object でロックを取得しますx
  • T2object でロックを取得しようとしますx。このスレッドは不運です。永遠に続くことがわかっているからです。
  • T1変更しますx。これが新しいオブジェクトになりました。私はそれを呼びますx'1
  • T3ロックを取得しようとします。しかし、変数xは実際には object を参照していますx'1。誰もロックオンしていないx'1ので、彼はパスします。
  • T3変更しますx。という名前の新しいオブジェクトになりましたx'3
  • T1コンソールに書き込みます。
  • T3コンソールに書き込みます。
  • T1xオブジェクトを指す変数 でロックを解放しようとします... x'3
  • オブジェクトはMonitor言う: 「ねえ、何をしていると思いますか? あなたはそのロックを持っていません! この例外の小さな吸盤を食べてください」
  • フィン
于 2009-05-12T15:57:10.157 に答える