Странное поведение при изменении исключительно заблокированного объекта - Monitor.Enter (x) - PullRequest
1 голос
/ 06 мая 2009

Я хотел посмотреть, что произойдет, если вы измените ссылку на объект, заблокированный исключительно Monitor.Enter (). Как и ожидалось, было сгенерировано исключение SynchronizationLockException. Но я был удивлен, увидев несколько потоков, проходящих мимо Монитора до того, как возникло исключение.

Вот что делает код ниже.

  1. создать и запустить 100 потоков
  2. заставляет весь поток ждать, пока не будет установлено ManualResetEvent.
  3. установить ManualResetEvent - Вроде как размахивать зеленым флагом на гонке Инди.
  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??

Ответы [ 3 ]

4 голосов
/ 06 мая 2009

То, что вы видите, - это ожидаемое поведение. В фактической переменной, используемой для передачи ссылки в Monitor.Enter(), нет ничего особенного. Изменение ссылки не должно помешать другим потокам получить эксклюзивную блокировку, поскольку переменная имеет новое значение и эта ссылка нигде не заблокирована.

Ваша проблема связана с Exit, поскольку поток, вызывающий Exit, не имеет монопольной блокировки передаваемой ссылки. Другой поток может иметь блокировку, но поток, в котором вы выполняете не.

Вот почему, как вы знаете, всегда лучше делать блокировку с переменной, ссылка на которую никогда не изменится. Если переменная вашего ресурса может измениться, используйте новую ссылку.

Это достаточно ясно?

2 голосов
/ 06 мая 2009

'x' является ссылкой на объект; это не сам объект. Когда вы делаете

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

Вы выбросили заблокированный объект, на который ранее ссылался x, и заставили x ссылаться на какой-то другой объект. Теперь, когда вы делаете

      Monitor.Exit(x);

Вы получаете исключение, потому что этот объект на самом деле не заблокирован - заблокированный вами объект теперь является мусором, собираемым сборщиком мусора.

1 голос
/ 12 мая 2009

Причина такого поведения в том, что вы меняете здесь значение 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 говорит: "Эй, как ты думаешь, что ты делаешь? У тебя нет этого замка!
  • Fin
...