Как работает блокировка?

388

Я вижу, что для использования объектов, которые не являются потокобезопасными, мы обмениваем код блокировкой следующим образом:

private static readonly Object obj = new Object();

lock (obj)
{
    // thread unsafe code
}

Итак, что происходит, когда несколько потоков обращаются к одному и тому же коду (предположим, что он запущен в веб-приложении ASP.NET). Они поставлены в очередь? Если да, то как долго они будут ждать?

Какое влияние влияет на использование блокировок?

Теги:
thread-safety
synchronization
locking

8 ответов

379
Лучший ответ

Оператор lock переведен на С# 3.0 следующим образом:

var temp = obj;

Monitor.Enter(temp);

try
{
    // body
}
finally
{
    Monitor.Exit(temp);
}

В С# 4.0 это изменилось, и теперь оно создается следующим образом:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp); 
    }
}

Подробнее о том, что Monitor.Enter делает здесь. Чтобы процитировать MSDN:

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

Метод Monitor.Enter будет бесконечно ждать; он не будет тайм-аут.

  • 7
    Согласно MSDN, использование ключевого слова lock (C #) или SyncLock (Visual Basic), как правило, предпочтительнее прямого использования класса Monitor, поскольку блокировка или SyncLock более лаконична, а также потому, что блокировка или SyncLock обеспечивает освобождение базового монитора, даже если защищенный код вызывает исключение. Это достигается с помощью ключевого слова finally, которое выполняет связанный с ним блок кода независимо от того, было ли выброшено исключение. " msdn.microsoft.com/en-us/library/ms173179.aspx
  • 8
    Какой смысл в var temp = obj; линия. поскольку это просто ссылка для начала, что хорошего в том, чтобы заставить другого делать?
Показать ещё 6 комментариев
206

Это проще, чем вы думаете.

Согласно Microsoft: Ключевое слово lock гарантирует, что один поток не войдет в критический раздел кода, а другой поток находится в критическом разделе. Если другой поток пытается ввести заблокированный код, он будет ждать, блокировать, пока объект не будет выпущен.

Ключевое слово lock вызывает Enter в начале блока и Exit в конце блока. lock ключевое слово на самом деле обрабатывает Monitor класс в конце.

Например:

private static readonly Object obj = new Object();

lock (obj)
{
    // critical section
}

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

  • 5
    мы должны создать фиктивный объект для блокировки или мы можем заблокировать существующую переменную в контексте?
  • 8
    @batmaci - Блокировка отдельного фиктивного объекта дает вам гарантию, что никто не блокирует этот объект. Если вы заблокируете данные и этот же фрагмент данных будет виден извне, вы потеряете эту гарантию.
Показать ещё 1 комментарий
41

Нет, они не поставлены в очередь, они спали

Оператор блокировки формы

lock (x) ... 

где x - выражение ссылочного типа, точно эквивалентно

var temp = x;
System.Threading.Monitor.Enter(temp); 
try { ... } 
finally { System.Threading.Monitor.Exit(temp); }

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

Монитор полностью написан в .net, поэтому достаточно быстро, также посмотрите класс Monitor с reflector для более подробной информации

  • 6
    Обратите внимание, что код, выданный для оператора lock немного изменился в C # 4: blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
  • 0
    @ArsenMkrt, они не хранятся в «заблокированном» состоянии «Очередь». Я думаю, что есть некоторая разница между режимом сна и блоком, не так ли?
Показать ещё 6 комментариев
25

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

10

Влияние производительности зависит от способа блокировки. Здесь вы можете найти хороший список оптимизаций: http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/

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

  • 1
    Но попытка написать код с низким уровнем блокировки может часто приводить к тонким, трудно обнаруживаемым и исправляемым ошибкам, даже если вы являетесь экспертом в этой области. Использование блокировки часто является меньшим из двух зол. Вы должны заблокировать ровно столько, сколько вам нужно, не больше, не меньше!
  • 0
    @LukeH: Существуют некоторые схемы использования, в которых код с низкой блокировкой может быть очень простым и легким [ do { oldValue = thing; newValue = updated(oldValue); } while (CompareExchange(ref thing, newValue, oldValue) != oldValue ]. Самая большая опасность состоит в том, что если требования развиваются сверх того, что могут обрабатывать такие методы, может быть сложно адаптировать код для их обработки.
Показать ещё 1 комментарий
7

Оператор lock преобразуется в вызовы методов Enter и Exit Monitor.

Оператор lock будет ждать неограниченное время для освобождения объекта блокировки.

7

Часть внутри оператора блокировки может выполняться только одним потоком, поэтому все остальные потоки будут бесконечно ждать, пока поток, удерживающий блокировку, не будет завершен. Это может привести к так называемому тупику.

4

замок на самом деле скрытые монитор класса.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню