Ответ
Конструкция lock — это синтаксический сахар для работы с классом Monitor. Компилятор C# разворачивает lock в вызовы Monitor.Enter и Monitor.Exit внутри блока try-finally, что гарантирует освобождение блокировки даже при исключении.
Исходный код:
private readonly object _syncRoot = new object();
lock (_syncRoot)
{
// Критическая секция
sharedResource.Modify();
}
Во что это компилируется (примерный эквивалент):
bool lockTaken = false;
try
{
Monitor.Enter(_syncRoot, ref lockTaken);
// Критическая секция
sharedResource.Modify();
}
finally
{
if (lockTaken)
Monitor.Exit(_syncRoot);
}
Ключевые моменты:
- Переменная
lockTaken(передаваемая по ссылке) нужна для защиты от ситуации, когда исключение возникнет между попыткой захватаMonitor.Enterи установкой флага. Без неё вfinallyмог бы вызватьсяMonitor.Exitдля не захваченной блокировки, что привело бы кSynchronizationLockException. lockв C# реализует повторно входимую (reentrant) блокировку: поток, уже владеющий блокировкой, может снова войти вlockна том же объекте.- Для блокировки следует использовать отдельный private объект, а не
thisили публичные объекты, чтобы избежать случайных взаимоблокировок.
Важно: lock работает только в рамках одного процесса. Для межпроцессной синхронизации нужны Mutex или Semaphore.
Ответ 18+ 🔞
Да ты посмотри, во что компилятор этот наш, блядь, разворачивает обычный-то lock! Ну, казалось бы, обернул код в lock и спи спокойно, ан нет, сука!
Вот пишешь ты, значит, красивенько:
private readonly object _syncRoot = new object();
lock (_syncRoot)
{
// Критическая секция
sharedResource.Modify();
}
А потом берёшь этот код, сука, и смотришь, во что его превращают, эти, блядь, компиляторщики! А превращают они его, ёпта, вот в такую портянку:
bool lockTaken = false;
try
{
Monitor.Enter(_syncRoot, ref lockTaken);
// Критическая секция
sharedResource.Modify();
}
finally
{
if (lockTaken)
Monitor.Exit(_syncRoot);
}
И знаешь, зачем эта хуйня с lockTaken? А чтобы, блядь, не выстрелить себе в ногу! Представь: Monitor.Enter начал выполняться, но прямо посреди его работы, сука, исключение выскочило — и блокировка-то не захватилась! А код в finally по старой схеме тупо вызвал бы Monitor.Exit и получил бы ты в лицо SynchronizationLockException. Вот и придумали эту переменную-флаг, которая по ссылке передаётся, чтобы точно знать: захватил — не захватил. Умно, блядь, надо признать.
А ещё lock он, сука, повторно входимый! Это ж гениально. Если поток уже владеет этой сракой-блокировкой, он может зайти в неё ещё раз, и всё будет ок. Не упрётся сам в себя, как идиот.
И главное, запомни нахуй: не используй this или публичные объекты для блокировки! Выдели себе отдельный, приватный объект, вроде _syncRoot, и спи спокойно. А то начнёшь блокироваться на публичном поле, другой поток тоже за него схватится — и понеслась ебля с взаимоблокировками, разбирай потом, кто кого ждал.
И да, это всё работает только в рамках одного процесса, блядь. Хочешь между процессами синхронизироваться — вали на Mutex или Semaphore. lock тут бессилен, как мой сосед после пятой рюмки.
Вот так-то, дружок. Казалось бы, lock, а под капотом — целый ёперный театр!