Что передаётся в оператор `lock` в 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).

Ответ 18+ 🔞

Слушай, ну вот объясняют же люди, как с этим lock'ом работать, а потом смотришь на код — и волосы дыбом, ёпта! Будто человек впервые слышит, что потоки существуют. Ладно, щас разжую.

Вот смотри, есть у тебя критическая секция — кусок кода, куда нафиг нельзя пускать всех сразу, как в переполненный сортир на рок-фестивале. Чтобы не было давки и говна по стенам, ты ставишь замок. В C# это оператор lock. А в этот lock ты суёшь объект-маркер, он же монитор. Это как ключ от того самого сортира: у кого ключ — тот и делает свои дела, остальные ждут, как лохи, с поджатыми ягодицами.

А теперь, блядь, самое важное — что за объект ты туда пихаешь:

  • Делай его приватным и только для этого. private readonly object _lockObject = new object(); — вот твой друг. Не используй эту хуйню больше ни для чего! Иначе какой-нибудь умник из другого класса возьмёт и заблокирует твой же объект, и будет взаимная блокировка, пиздец и deadlock. А ты потом голову ломаешь, почему всё зависло.
  • Только ссылочный тип, нахуй! Не вздумай пихать int или bool — это типы значения, они не катят. Нужен именно объект в куче.
  • Забудь как страшный сон lock(this). Это классический распиздяйский приём. this — публичный экземпляр твоего класса. Любой другой код может его заблокировать, и твой поток будет ждать у разбитого корыта. Пидарас тот, кто так делает.
  • Не используй typeof(MyClass) и уж тем более строки. lock(typeof(MyClass)) — это глобальная блокировка на весь тип, овердохуища проблем. А lock("myLock") — это вообще пиздец, потому что строки интернируются, и одна и та же строка во всём приложении — один объект. Представь, ты блокируешься по строке "lock", а в соседней библиотеке, о которой ты не знаешь, тоже кто-то по "lock" блокируется. И вы оба ебётесь, не понимая, кто кого держит. Короче, ядрёна вошь.

Вот как надо, по-человечески:

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

    public void Increment()
    {
        lock (_lockObject) // Захватили ключ
        {
            _count++; // Сделали дело быстро
        } // Освободили ключ — следующий, заходи!
    }

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

Всё, элементарно, Ватсон. Создал объектик, используешь его в lock. Никакой магии.

Если задачи посерьёзнее — там уже можно на Monitor ручками позырить (который внутри lock и работает), или на Mutex для синхронизации между процессами, или на SemaphoreSlim всякий. Но для обычных бытовых случаев lock с правильным объектом — твоя палочка-выручалочка. Главное — не выебывайся и не пытайся быть умнее системы, а то будет тебе хиросима, а не многопоточность.