Як саме працює замок?


526

Я бачу, що для використання об'єктів, які не є безпечними для потоків, ми обертаємо код таким замком:

private static readonly Object obj = new Object();

lock (obj)
{
    // thread unsafe code
}

Отже, що відбувається, коли декілька потоків отримують доступ до одного і того ж коду (припустимо, він працює у веб-додатку ASP.NET). Вони стоять у черзі? Якщо так, як довго вони будуть чекати?

Який вплив на продуктивність через використання замків?


1
^ мертве посилання, див .: jonskeet.uk/csharp/threads/index.html
Іван Павічич

Відповіді:


447

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 гарантує, що базовий монітор звільнений, навіть якщо захищений код кидає виняток. Це робиться за допомогою останнього ключового слова, яке виконує пов'язаний з ним код коду незалежно від того, викинуто виняток. " msdn.microsoft.com/en-us/library/ms173179.aspx
Aiden

10
У чому сенс var temp = obj; рядок. оскільки для початку це лише спроба, що корисного для того, щоб зробити ще одного?
priehl

11
@priehl Це дозволяє користувачеві змінюватися objбез усієї системи в тупик.
Стівен

7
@Joymon зрештою, кожна мовна особливість - синтаксичний цукор. Мовні особливості полягають у тому, щоб зробити розробників більш продуктивними та зробити додатки більш рентабельними, як і функцію блокування.
Стівен

2
Правильно. У цьому і полягає вся мета- lockStatement і Monitor: щоб ви могли виконати операцію в одному потоці, не турбуючись про те, що інший потік перериває його.
Dizzy H. Muffin

285

Це простіше, ніж ви думаєте.

На думку Microsoft : lockКлючове слово забезпечує, що один потік не вводить критичний розділ коду, а інший - у критичний розділ. Якщо інший потік спробує ввести заблокований код, він буде чекати, блокувати, поки об’єкт не буде звільнений.

lockКлючове слово викликає Enterна початку блоку і Exitв кінці блоку. lockключове слово насправді обробляє Monitorклас на задньому кінці.

Наприклад:

private static readonly Object obj = new Object();

lock (obj)
{
    // critical section
}

У наведеному вище коді спочатку потік потрапляє у критичний розділ, а потім він блокується obj. Коли намагається ввести інший потік, він також спробує заблокувати obj, який вже заблокований першим потоком. Другий потік доведеться почекати, коли перший потік вийде obj. Коли перша нитка відійде, то інша нитка заблокується objі увійде в критичну секцію.


9
чи слід створити фіктивний об’єкт для блокування чи ми можемо заблокувати існуючу змінну в контексті?
batmaci

9
@batmaci - Блокування окремого приватного манекенного об’єкта дає вам гарантію, що ніхто більше не заблокує цей об’єкт. Якщо ви заблокуєте дані, і цей самий фрагмент даних буде видно зовні, ви втратите гарантію.
Умар Аббас

8
Що станеться, якщо замок відпустить більше одного процесу? Чи чекають процеси в черзі, щоб вони блокували критичний розділ у порядку FIFO?
jstuardo

@jstuardo - Вони стоять у черзі, але замовлення не гарантовано є FIFO. Перевірте це посилання: albahari.com/threading/part2.aspx
Умар Аббас

Скопійовано без атрибуції з net-informations.com/faq/qk/lock.htm
Martijn Pieters

47

Ні, вони не стоять у черзі, вони сплять

Запис бланка форми

lock (x) ... 

де x - вираз еталонного типу, точно еквівалентний

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

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

Монітор повністю написана в .net , так що досить швидко, а також дивитися на клас моніторів з відбивачем для отримання більш докладної інформації


6
Зауважте, що код, викинутий для lockзаяви, незначно змінився на C # 4: blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
ЛукаH

@ArsenMkrt, їх не утримують у черзі "Блокований" стан. Я думаю, що між
режимом

яка різниця ти маєш на увазі @Mohanavel?
Арсен Мкртчян

1
У цьому не було питання. Питання стосувалося ключового слова "замок". Припустимо, процес увійшов у розділ "замок". Це означає, що процес блокує цей фрагмент коду, і жоден інший процес не зможе увійти в цей розділ до тих пір, поки цей блокування не буде звільнено. Ну .... тепер, ще 2 процеси намагаються ввести той самий блок. Оскільки воно захищене ключовим словом "замок", вони будуть чекати, відповідно до того, що було сказано на цьому форумі. Коли перший процес звільняє замок. Який процес входить до блоку? перший, який намагався ввести, або останній?
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]. Найбільша небезпека полягає в тому, що якщо вимоги розвиваються за межами того, з якими такими методами можна впоратися, може бути важко адаптувати код для цього.
supercat

Посилання зірвано.
CarenRose

8

Частина в операторі блокування може бути виконана лише одним потоком, тому всі інші потоки будуть чекати нескінченно довго, щоб нитка, що тримає замок, закінчилася. Це може призвести до так званого глухого кута.


8

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

lockЗаява буде чекати невизначений час для блокування об'єкта повинен бути звільнений.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.