Во что разворачивается конструкция lock в C#?

Ответ

Конструкция 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, а под капотом — целый ёперный театр!