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

«Во что разворачивается конструкция lock в C#?» — вопрос из категории Многопоточность, который задают на 25% собеседований 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.