Операторы блокировки С#

Когда поток пытается войти в критическую секцию и получить блокировку, что он на самом деле делает?

Я спрашиваю об этом, потому что обычно создаю объект (типа объекта), который будет служить только для целей блокировки. Рассмотрим следующее: я хочу написать метод, который принимает коллекцию, и объект, который будет служить в качестве объекта блокировки, поэтому все манипуляции с коллекцией внутри этого метода будут объявлены внутри критической секции, которая будет заблокирована этим данным объектом.

Должен ли я передать этот блокирующий объект, используя «ref», или достаточно передать ссылочную копию этого объекта? Другими словами, поскольку оператор блокировки используется только со ссылочными типами, проверяет ли механизм значение объекта, на который указывает ссылка, или он проверяет значение указателя? потому что очевидно, что при передаче объекта без "ref" я фактически получаю копию ссылки, а не саму ссылку.


person Mikey S.    schedule 14.08.2011    source источник


Ответы (4)


Вот типичный шаблон, которому вы можете следовать для блокировки. По сути, вы можете создать объект блокировки, который используется для блокировки доступа к вашему критическому разделу (который, как сказал @Hans, не защищает объект, над которым вы работаете, - он просто обрабатывает блокировку).

class ThreadSafe
{
  static readonly object _locker = new object();
  static int _val1, _val2;

  static void Go()
  {
    lock (_locker)
    {
      if (_val2 != 0) Console.WriteLine (_val1 / _val2);
      _val2 = 0;
    }
  }
}

Этот пример взят из онлайн-книги Джозефа Альбахари по многопоточности. Он предоставляет отличный обзор того, что происходит, когда вы создаете оператор lock, а также несколько советов/рекомендаций о том, как лучше всего его оптимизировать. Определенно настоятельно рекомендуется к прочтению.

Согласно Альбахари, снова оператор lock переводится в .NET 4 как:

bool lockTaken = false;
try
{
  Monitor.Enter (_locker, ref lockTaken);
  // Do your stuff...
}
finally { if (lockTaken) Monitor.Exit (_locker); }

На самом деле это безопаснее, чем прямой Monitor.Enter, а затем вызов Monitor.Exit в вашем finally, поэтому он был добавлен в .NET 4.

person David Hoerster    schedule 14.08.2011

Достаточно заблокировать object, не передавая ref. На самом деле lock вызывает Monitor.Enter в начале блока и Monitor.Exit при выходе.

Надеюсь это поможет.

person Tigran    schedule 14.08.2011

MSDN говорит здесь о блокировке

Используйте Enter, чтобы получить монитор для объекта, переданного в качестве параметра. Если другой поток выполнил Enter для объекта, но еще не выполнил соответствующий Exit, текущий поток будет заблокирован до тех пор, пока другой поток не освободит объект. Один и тот же поток может вызывать Enter более одного раза без блокировки; однако должно быть выполнено такое же количество вызовов Exit, прежде чем другие потоки, ожидающие объект, разблокируются.

что означает, что речь идет не о ссылке или указателе, а о фактическом объекте, на который указывает ссылка, поэтому вам не нужно будет передавать, поскольку ref простой проход по ссылке будет работать

Относительно того, что на самом деле происходит внутри замка, см. ответ на этот вопрос, в котором говорится

«Операция блокировки преобразуется C# в следующее:»

var temp = obj;

Monitor.Enter(temp);

try
{
    // body
}
finally
{
    Monitor.Exit(temp);
}
person Haris Hasan    schedule 14.08.2011

Должен ли я передать этот блокирующий объект, используя «ref», или достаточно передать ссылочную копию этого объекта?

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

Но если вы действительно хотите передать объект блокировки, вам не нужно использовать ref, как указывали другие. Блокировка выполняется для экземпляра объекта, а не для переменной, содержащей ссылку на него.

person svick    schedule 14.08.2011