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

问题描述 投票:1回答:3

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

以下是以下代码的作用。

  1. 创建并启动100个线程
  2. 使所有线程等待,直到设置了ManualResetEvent。
  3. 设置ManualResetEvent - 有点像在Indy比赛中挥动绿旗。
  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);
    }
  }
}

Console Output, looks like some threads got past the monitor??

c# multithreading monitor
3个回答
4
投票

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

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

如你所知,这就是为什么总是最好用变量进行锁定,而变量的引用永远不会改变。如果资源的变量可能会更改,请使用新引用。

这够清楚了吗?


2
投票

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

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

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

      Monitor.Exit(x);

您得到异常,因为此对象实际上并未锁定 - 您锁定的对象现在是垃圾收集器收集的垃圾。


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对象说:“嘿,你觉得你在做什么?你没有那个锁!吃这个例外的小傻瓜”
  • 结束
© www.soinside.com 2019 - 2024. All rights reserved.