更改专有锁定对象时的奇怪行为 - Monitor.Enter(x)

时间:2009-05-06 04:25:46

标签: c# multithreading monitor

我想看看如果更改Monitor.Enter()独占锁定的对象的引用会发生什么。正如所料,抛出了SynchronizationLockException。但是我惊讶地看到几个线程在异常被抛出之前越过Monitor。

以下是以下代码的作用。

  1. 创建并启动100个帖子
  2. 使所有线程等待,直到设置了ManualResetEvent。
  3. 设置ManualResetEvent - 有点像在Indy比赛中挥舞绿旗。
  4. 在x
  5. 上设置独占锁(Monitor.Enter(x))
  6. 更改x的参考。
  7. 此时我预计会抛出某种异常,但直到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??

3 个答案:

答案 0 :(得分:4)

您所看到的是预期的行为。用于将引用传递给Monitor.Enter()的实际变量没有什么特别之处。更改引用不应阻止其他线程获取独占锁,因为该变量具有新值,并且该引用未在任何位置锁定。

你的问题是Exit,因为调用Exit的线程没有对传入的引用进行独占锁定。另一个线程可能对它有锁定,但线程你“执行中没有。”

正如您所知,这就是为什么始终最好使用变量进行锁定,而变量的引用永远不会改变。如果资源的变量可能会发生变化,请使用新的参考。

这是否足够清楚?

答案 1 :(得分:2)

'x'是对象的引用;它不是对象本身。当你这样做

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

您丢弃了之前引用的x的锁定对象,并使x引用其他对象。现在当你做

      Monitor.Exit(x);

你得到了异常,因为这个对象实际上并没有被锁定 - 你锁定的对象现在是垃圾收集器收集的垃圾。

答案 2 :(得分:1)

此行为的原因是您在此处更改x的值:

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

所以当你到达

Monitor.Exit(x)

您正在使用其他对象释放锁定。这就像你用一把钥匙挂锁,并尝试用另一个挂锁的钥匙取下挂锁。

此外,与其他线程相比,Console.Writeline是一个昂贵的指令,因此有几个线程在其中一个线程接近终点线之前进入监视器。

以下是一个示例运行:

你开始了一堆线程。

  • 线程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对象说:“嘿,你觉得你在做什么?你没有锁定!吃这个例外小傻逼”