2009-05-06 4 views
1

Monitor.Enter()によって排他的にロックされたオブジェクトの参照を変更した場合、予想通り、SynchronizationLockExceptionがスローされました。しかし、例外がスローされる前に、いくつかのスレッドがMonitorを通過するのを見て驚いた。ロックされたオブジェクトを排他的に変更するときの変な動作 - Monitor.Enter(x)

以下のコードは次のとおりです。

  1. 100件のスレッド
  2. を作成して開始しManualResetEventが設定されるまで、すべてのスレッドを待機させます。
  3. ManualResetEvent - Indyレースで緑色の旗を振るようなものを設定します。
  4. 排他ロック(Monitor.Enter(x))を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); 
    } 
    } 
} 

Console Output, looks like some threads got past the monitor?? http://img26.imageshack.us/img26/3633/outputs.jpg

答えて

4

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

Exitを呼び出すスレッドには、参照が渡される参照に排他ロックが設定されていないため、問題が発生します。

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

これで十分ですか?

+0

ので変更Monitor.Enter()に渡される変数の参照は、ロックを解除するようなものです。まあ、Monitor.Endで例外がスローされる以外は例外です。それは理にかなっている。ありがとうございました。理由はわかりませんが、ロックされた変数への参照を変更するとすぐに例外がスローされると思っていました。 –

+2

いいえ、あなたはロックを解除していません。それを次のように考えてください。あなたはあなたの家に戸を閉じ込めました。あなたは大工を連れて来てドアを全部交換しました。新しいドアがロックされていないときにあなたは驚いた: – bdonlan

+0

Behaiorallyおそらくそれのようですが、それは実際にそれをやっていません。他のスレッドがロックしようとするオブジェクトの参照を変更するだけです。 –

2

'x'はオブジェクトへの参照です。それはオブジェクトそのものではありません。あなたがしたとき

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

これまでに参照したロックされたオブジェクトを投げ捨て、xは他のオブジェクトを参照しました。あなたは

 Monitor.Exit(x); 

を行うときに、このオブジェクトが実際にロックされていないので、今、あなたは例外を取得 - あなたがロックされたオブジェクトは現在、ガベージコレクタによって収集されるごみです。

+3

実際には、私はそれが収集されると言ってすぐにはないでしょう、モニタークラスは、どこかへの参照を持っているかもしれません(実際には、 。それにもかかわらず、あなたの答えはそうでなければ正しいです。 –

1

この挙動の理由は、あなたがここにxの値を変更しているということです:あなたは

Monitor.Exit(x) 

に着くとき

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

は、だから、別のオブジェクトにロックを解除しています。それは、1つのキーで南京錠を置き、別の南京錠からキーで南京錠を取り外そうとするのと同じです。

さらに、Console.Writelineは他のスレッドと比較して高価な命令であるため、複数のスレッドが1つのスレッドが終了ラインに近づく前にモニターに入ることがあります。

あなたがスレッドの束を開始します。

は、ここでの例の実行です。

  • スレッドT1は、オブジェクトxでロックを取得します。
  • T2は、オブジェクトxでロックを取得しようとします。この糸は運が悪いです。私たちはそれが永遠に待つつもりであることを知っています。
  • T1変更x。今は新しいオブジェクトです。私はそれをx'1と呼ぶでしょう。
  • T3は、ロックを取得しようとします。しかし、変数xは実際にオブジェクトx'1を参照しています。誰もx'1にロックしていないので、彼は合格する。
  • T3変更x。今はx'3という新しいオブジェクトです。
  • T1コンソールに書き込みます。
  • T3コンソールに書き込みます。
  • T1は、オブジェクトを指す変数xでロックを解放しようとします。x'3
  • Monitorオブジェクトは言う:「?!ねえ、あなたはあなたがそのロックを持っていないあなたがやっていることだと思います。この例外少し吸盤を食べる」
  • フィン
関連する問題