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

527

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

private static readonly Object obj = new Object();

lock (obj)
{
    // thread unsafe code
}

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

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

NLV
источник
1
^ мертвая ссылка, см. jonskeet.uk/csharp/threads/index.html
Иван Павичич

Ответы:

448

Это lockзаявление переведено C # 3.0 на следующее:

var temp = obj;

Monitor.Enter(temp);

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

В C # 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Метод будет ждать бесконечно; это не будет время ожидания.

Стивен
источник
15
В соответствии с MSDN «Использование ключевого слова lock (C #) или SyncLock (Visual Basic), как правило, предпочтительнее прямого использования класса Monitor, поскольку блокировка или SyncLock более лаконична, а также потому, что блокировка или SyncLock обеспечивает освобождение базового монитора, даже если защищенный код вызывает исключение. Это достигается с помощью ключевого слова finally, которое выполняет связанный с ним блок кода независимо от того, было ли выброшено исключение. " msdn.microsoft.com/en-us/library/ms173179.aspx
Эйден Стридом,
10
Какой смысл в var temp = obj; линия. поскольку это просто ссылка для начала, что хорошего в том, чтобы заставить другого делать?
Приэль
11
@priehl Это позволяет пользователю изменять objбез всякой блокировки системы.
Стивен
7
@Joymon в конце концов, каждая языковая особенность - синтаксический сахар. Языковые функции предназначены для того, чтобы сделать разработчиков более продуктивными и сделать приложения более удобными в обслуживании, как и функция блокировки.
Стивен
2
Правильный. В этом вся цель lock-statement и Monitor: чтобы вы могли выполнять операцию в одном потоке, не беспокоясь о том, что другой поток его испортит.
Диззи Х.
285

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

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

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

Например:

private static readonly Object obj = new Object();

lock (obj)
{
    // critical section
}

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

Умар Аббас
источник
9
мы должны создать фиктивный объект для блокировки или мы можем заблокировать существующую переменную в контексте?
Батмачи
9
@batmaci - Блокировка отдельного частного фиктивного объекта дает вам гарантию, что никто не блокирует этот объект. Если вы заблокируете данные и этот же фрагмент данных будет виден извне, вы потеряете эту гарантию.
Умар Аббас
8
Что произойдет, если более одного процесса ожидает снятия блокировки? Процессы ожидания поставлены в очередь, так что они будут блокировать критическую секцию в порядке FIFO?
jstuardo
@jstuardo - они стоят в очереди, но порядок не гарантированно будет FIFO. Проверьте эту ссылку: albahari.com/threading/part2.aspx
Умар Аббас
Скопировано без указания авторства с net-informations.com/faq/qk/lock.htm
Мартин Питерс
47

Нет, они не стоят в очереди, они спят

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

lock (x) ... 

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

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

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

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

Арсен Мкртчян
источник
6
Обратите внимание, что код, использованный для этого lockутверждения, немного изменился в C # 4: blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
LukeH
@ArsenMkrt, они не хранятся в «заблокированном» состоянии «Очередь?». Я думаю, что есть некоторая разница между сном и состоянием блока, не так ли?
Mohanavel
Какую разницу вы имеете в виду @Mohanavel?
Арсен Мкртчян
1
Это был не вопрос. Вопрос касался ключевого слова "замок". Предположим, что процесс входит в раздел «блокировка». Это означает, что процесс блокирует этот фрагмент кода, и никакой другой процесс не сможет войти в этот раздел, пока не будет снята эта блокировка. Ну .... теперь еще 2 процесса пытаются войти в тот же блок. Так как он защищен ключевым словом "lock", они будут ждать, согласно тому, что было сказано на этом форуме. Когда первый процесс снимает блокировку. Какой процесс входит в блок? первый, который пытался войти или последний?
jstuardo
1
Я предполагаю, что вы имеете в виду нить вместо процесса ... если так, то ответ - Нет, нет гарантии, какой из них будет введен ... подробнее здесь stackoverflow.com/questions/4228864/…
Арсен Мкртчян
29

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

Эндрю
источник
11

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

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

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

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

Mr47
источник
8

lockЗаявление переводятся на призывы к Enterи Exitметодам Monitor.

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

Паоло Тедеско
источник