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

Ответ

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, когда нужно ограничить не единицей, а заданным числом потоков.

Ответ 18+ 🔞

А, ну вот, опять эти синхронизационные штуки, про которые все путаются, как слепые котята в коробке! Слушай, давай разложу по полочкам, чтобы наконец-то дошло, в чём разница между Monitor, Mutex и Semaphore. Это ж элементарно, Ватсон, если не забивать голову лишней хуйнёй.

Monitor — это как наш местный, доморощенный засов на двери в сортире. Работает только внутри одного процесса, в нашей родной квартире. Подошёл к двери, щёлк — lock(_object) — и ты внутри. Больше никто не зайдёт, пока ты не выйдешь. Легковесный, быстрый, для своих. Просто lock — это и есть обёртка над Monitor.

private object _lockObj = new object(); // Наш засов

void UseToilet()
{
    lock (_lockObj) // Щёлк — закрылся
    {
        // Делай что хочешь, тут только ты один
        // Остальные ждут снаружи, как дурачки
    }
    // Выходишь — засов открывается сам
}

Mutex — это уже серьёзнее. Это как именной навесной замок на каком-нибудь общем сарае во дворе. Может работать между разными процессами (если именованный). У него есть «хозяин». Взять замок (WaitOne) может только один чувак — либо из твоего приложения, либо из соседнего. Пока он не откроет (ReleaseMutex), все остальные будут мотаться вокруг и материться. Идеально, чтобы гарантировать, что твоё приложение в единственном экземпляре запущено, например.

// Глобальный замок на весь компьютер
using var mutex = new Mutex(false, "Global\MySuperAppMutex");
// Пытаемся взять замок, ждём не больше секунды, чтобы не зависнуть нахуй
if (!mutex.WaitOne(TimeSpan.FromSeconds(1)))
{
    // Не получилось — значит, другой экземпляр уже висит на этом замке
    Console.WriteLine("Да отъебись, я уже работаю!");
    return;
}
try
{
    // Ага, я здесь хозяин, делаю что надо
    // Другие и процессы, и потоки — все ждут
}
finally
{
    mutex.ReleaseMutex(); // Открыл замок, теперь следующий может попробовать
}

Semaphore — это вообще отдельная песня. Представь себе турникет в метро. Их не один, а несколько. Semaphore говорит: «Одновременно внутри может быть не больше N потоков». Задал, например, 3 — и первые три прошли. Четвёртый будет ждать, пока кто-то из первых не выйдет (Release). SemaphoreSlim — его младший брат, для работы в рамках одного процесса, обычно с асинхронщиной.

Используется, когда у тебя ограниченный пул чего-то: подключений к базе, потоков на обработку файлов, мест на парковке. Чтобы всё не легло, как говно в проруби, от слишком большого наплыва.

// Сделаем семафор на 3 места, типа машин на парковке
private static SemaphoreSlim _parking = new SemaphoreSlim(3, 3);

async Task ParkCarAsync()
{
    await _parking.WaitAsync(); // Ждём свободное место. Если все 3 заняты — стопимся тут
    try
    {
        // Ура, заехали на парковку, делаем свои дела
        await ProcessCarAsync();
    }
    finally
    {
        _parking.Release(); // Выехали, освободили место для следующего
    }
}

Короче, сводка для тех, кто дочитал и всё равно нихуя не понял:

Штука Где работает Сколько пускает Зачем нужно
Monitor (lock) Внутри одного процесса Только 1 поток Быстро и просто отгородиться от соседних потоков в своём приложении.
Mutex Может и между процессами Только 1 поток/процесс Чтобы что-то было в единственном экземпляре на всю систему. Или для межпроцессной эксклюзивной блокировки.
Semaphore Может и между процессами N потоков (сколько скажешь) Чтобы не пускать всех сразу, а ограничить одновременный доступ, как турникетом.

Итог: Не усложняй. В 95% случаев внутри своего приложения тебе хватит обычного lock (то есть Monitor). Полез глобально что-то блокировать — смотри в сторону Mutex. Нужно пропускать по N штук, а не по одному — бери Semaphore или SemaphoreSlim. Всё, вопрос закрыт, можно идти пить чай.