В чем разница между Monitor, Mutex и Semaphore в .NET?

«В чем разница между Monitor, Mutex и Semaphore в .NET?» — вопрос из категории Многопоточность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Monitor, Mutex и Semaphore — это примитивы синхронизации в .NET для управления доступом к общим ресурсам в многопоточной среде. Их ключевые различия — в области действия (процесс/система) и количестве потоков, которым разрешен одновременный доступ.

1. Monitor (Внутрипроцессный, эксклюзивный доступ)

  • Область действия: Только в пределах одного процесса (AppDomain).
  • Доступ: Позволяет войти в критическую секцию только одному потоку.
  • Использование: Базовый механизм для lock. Легковесный.
private object _lockObj = new object();

void AccessResource()
{
    lock (_lockObj) // Использует Monitor внутри
    {
        // Только один поток здесь
    }
}

2. Mutex (Межпроцессный, эксклюзивный доступ)

  • Область действия: Может быть системным (именованным), работая между разными процессами.
  • Доступ: Позволяет войти в критическую секцию только одному потоку (или процессу). Имеет понятие "владельца".
  • Использование: Для синхронизации между процессами (например, гарантия, что только один экземпляр приложения запущен).
// Именованный мьютекс для межпроцессной синхронизации
using var mutex = new Mutex(false, "Global\MyAppSingletonMutex");
if (!mutex.WaitOne(TimeSpan.FromSeconds(1))) // Таймаут для избежания deadlock
{
    Console.WriteLine("Другой экземпляр приложения уже запущен.");
    return;
}
try
{
    // Код, выполняемый только в одном экземпляре приложения
}
finally
{
    mutex.ReleaseMutex();
}

3. Semaphore (Ограничение количества потоков)

  • Область действия: Может быть внутрипроцессным или именованным (межпроцессным).
  • Доступ: Ограничивает не единицей, а заданным максимальным количеством потоков, которые могут одновременно войти в секцию.
  • Использование: Для ограничения доступа к пулу ресурсов (например, не более 10 одновременных подключений к БД).
// Семафор, разрешающий одновременный доступ 3 потокам
private static SemaphoreSlim _pool = new SemaphoreSlim(3, 3);

async Task AccessDatabaseAsync()
{
    await _pool.WaitAsync(); // Ждем, если уже 3 потока внутри
try
    {
        // Не более 3 потоков выполняют эту часть одновременно
        await ExecuteQueryAsync();
    }
    finally
    {
        _pool.Release();
    }
}

Сводная таблица

Примитив Область действия Количество потоков Ключевое назначение
Monitor (lock) Внутри процесса 1 Быстрая синхронизация потоков в одном приложении.
Mutex Внутри или между процессами 1 Эксклюзивный доступ к ресурсу для одного потока/процесса.
Semaphore Внутри или между процессами N (задается) Ограничение пула одновременных операций (например, подключений, потоков).

Вывод: Выбирайте Monitor/lock для простых случаев внутри одного приложения. Используйте Mutex для межпроцессной блокировки. Применяйте Semaphore или SemaphoreSlim, когда нужно ограничить не единицей, а заданным числом потоков.