Что передаётся в оператор `lock` в C#?

«Что передаётся в оператор `lock` в C#?» — вопрос из категории Многопоточность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В оператор lock передаётся объект-маркер (монитор), который используется для синхронизации потоков при доступе к критической секции кода. Этот объект должен быть ссылочного типа.

Критически важные правила выбора объекта:

  • Приватный и только для блокировок: Объект должен быть private (или private static для статических методов) и не использоваться для других целей. Это предотвращает внешние взаимоблокировки.
  • Ссылочный тип: Примитивные типы значений (int, bool) не подходят. Используйте специально созданный экземпляр object.
  • Избегайте this: Использование lock(this) — антипаттерн, так как публичный экземпляр класса может быть заблокирован извне.
  • Избегайте Type и строк: lock(typeof(MyClass)) опасен из-за глобальности. lock("myLock") опасен из-за интернирования строк — одна литеральная строка во всем приложении будет одним объектом.

Правильный пример:

public class ThreadSafeCounter
{
    private int _count = 0;
    private readonly object _lockObject = new object(); // Специальный объект для блокировки

    public void Increment()
    {
        lock (_lockObject) // _lockObject выступает маркером для входа в критическую секцию
        {
            _count++; // Эта операция теперь атомарна для всех потоков
        }
    }

    public int GetCount()
    {
        lock (_lockObject)
        {
            return _count;
        }
    }
}

Альтернативы: Для более сложных сценариев рассмотрите Monitor (который используется внутри lock), Mutex (для межпроцессной синхронизации) или примитивы из пространства имен System.Threading (например, SemaphoreSlim, ReaderWriterLockSlim).