← Назад к вопросам

В чем разница между lock, Semaphore и Mutex?

1.7 Middle🔥 202 комментариев
#Асинхронность и многопоточность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между lock, Semaphore и Mutex

В C# для синхронизации многопоточного доступа к общим ресурсам используются различные механизмы. lock, Semaphore и Mutex — это три фундаментальных примитива синхронизации, каждый со своей областью применения и особенностями реализации.

1. lock (Monitor) — базовая блокировка

lock — это ключевое слово C#, представляющее собой синтаксический сахар для класса Monitor из пространства имен System.Threading. Это самый простой и наиболее часто используемый механизм синхронизации.

Основные характеристики:

  • Рекурсивность: Поток, уже владеющий блокировкой, может повторно захватить её без взаимоблокировки (deadlock).
  • Приватность: Блокировка работает только в пределах одного процесса.
  • Бинарность: Обеспечивает эксклюзивный доступ — только один поток может владеть блокировкой в любой момент времени.
private readonly object _lockObject = new object();
private int _sharedResource = 0;

public void Increment()
{
    lock (_lockObject)
    {
        _sharedResource++;
        // Критическая секция защищена
    }
}

Когда использовать: Для защиты коротких критических секций внутри одного процесса, когда нужен эксклюзивный доступ к общему ресурсу.

2. Semaphore (Семафор) — счетчиковая блокировка

Semaphore — это примитив синхронизации, который ограничивает количество потоков, одновременно имеющих доступ к ресурсу. В отличие от lock, семафор позволяет задавать максимальное количество одновременных доступов.

Основные характеристики:

  • Счетчик: Имеет счетчик, определяющий, сколько потоков могут одновременно войти в критическую секцию.
  • Межпроцессность: Semaphore может быть именованным и использоваться для синхронизации между процессами.
  • Нерекурсивность: По умолчанию Semaphore не является рекурсивным.
// Разрешает одновременный доступ 3 потокам
private static Semaphore _semaphore = new Semaphore(3, 3);

public void AccessResource()
{
    _semaphore.WaitOne(); // Уменьшает счетчик
    try
    {
        // Работа с общим ресурсом
        Console.WriteLine($"Поток {Thread.CurrentThread.ManagedThreadId} работает с ресурсом");
        Thread.Sleep(1000);
    }
    finally
    {
        _semaphore.Release(); // Увеличивает счетчик
    }
}

Когда использовать: Когда нужно ограничить количество одновременных доступов к ресурсу (например, пул подключений к БД, ограничение параллельных HTTP-запросов).

3. Mutex (Взаимоисключающая блокировка)

Mutex (mutual exclusion) — примитив синхронизации, который гарантирует, что только один поток может владеть им в любой момент времени. По функциональности похож на lock, но имеет важные дополнительные возможности.

Основные характеристики:

  • Межпроцессность: Mutex может быть именованным и использоваться для синхронизации между разными процессами.
  • Рекурсивность: Как и lock, Mutex является рекурсивным.
  • Идентификация владельца: Mutex "помнит", какой поток его захватил.
  • Системный ресурс: Является более тяжеловесным, чем lock, так как использует объекты ядра ОС.
// Локальный мьютекс (в пределах процесса)
private static Mutex _localMutex = new Mutex();

// Именованный мьютекс для межпроцессной синхронизации
private static Mutex _namedMutex = new Mutex(false, "Global\\MyAppMutex");

public void ProcessData()
{
    _localMutex.WaitOne();
    try
    {
        // Критическая секция
        Console.WriteLine($"Поток {Thread.CurrentThread.ManagedThreadId} в критической секции");
    }
    finally
    {
        _localMutex.ReleaseMutex();
    }
}

Когда использовать: Для синхронизации между разными процессами или когда нужна межпроцессная блокировка. Также может использоваться вместо lock, когда требуется более явный контроль.

Сравнительная таблица

Критерийlock (Monitor)SemaphoreMutex
Тип доступаЭксклюзивный (1 поток)Ограниченный (N потоков)Эксклюзивный (1 поток)
МежпроцессностьНетДа (именованный)Да (именованный)
РекурсивностьДаНет (по умолчанию)Да
ПроизводительностьВысокая (пользовательский режим)Средняя (ядро ОС)Ниже (ядро ОС)
ОсвобождениеАвтоматическое при выходе из блокаЯвный вызов Release()Явный вызов ReleaseMutex()
Использование в C#Через ключевое слово lockКласс Semaphore/SemaphoreSlimКласс Mutex

Практические рекомендации

  1. Используйте lock по умолчанию для большинства случаев синхронизации внутри одного процесса. Это наиболее эффективный и простой механизм.

  2. Выбирайте Semaphore или SemaphoreSlim (более легковесная версия) когда:

    • Нужно ограничить параллельный доступ к ресурсу (например, не более 5 одновременных запросов к API)
    • Реализуете паттерн "производитель-потребитель" с ограниченной очередью
  3. Применяйте Mutex когда:

    • Требуется синхронизация между разными процессами
    • Нужно гарантировать, что только один экземпляр приложения выполняется в системе
// Пример обеспечения единственного экземпляра приложения
static void Main()
{
    bool createdNew;
    using (var mutex = new Mutex(true, "MyAppSingleInstance", out createdNew))
    {
        if (!createdNew)
        {
            Console.WriteLine("Приложение уже запущено!");
            return;
        }
        
        // Основной код приложения
        Console.WriteLine("Приложение запущено");
        Console.ReadLine();
    }
}

Важные замечания

  • SemaphoreSlim — легковесная альтернатива Semaphore, рекомендуется для использования внутри одного процесса, так как не использует объекты ядра ОС до возникновения конкуренции.
  • Все примитивы синхронизации требуют аккуратного использования во избежание взаимоблокировок (deadlock).
  • Для более сложных сценариев рассмотрите асинхронные альтернативы (SemaphoreSlim.WaitAsync(), Monitor с async/await через другие механизмы).

Выбор конкретного механизма зависит от требований к производительности, необходимости межпроцессной синхронизации и семантики доступа к защищаемому ресурсу.