Почему Mutex может работать между процессами?

Ответ

Mutex (взаимное исключение) может синхронизировать потоки из разных процессов, потому что это объект уровня операционной системы, а не среды выполнения (CLR).

Ключевые отличия от lock/Monitor:

  • lock (Monitor): Работает только в пределах одного процесса и домена приложения (AppDomain), используя память кучи CLR.
  • Mutex: Создается в глобальном пространстве имен ядра ОС (например, в Windows). Разные процессы могут открыть хэндл одного и того же именованного объекта ядра.

Как это работает:

  1. При создании именованного Mutex ОС регистрирует его в своем внутреннем пространстве имен (например, Global\MyAppMutex).
  2. Любой процесс, знающий это имя, может открыть хэндл на тот же самый объект ядра.
  3. Операции WaitOne() и ReleaseMutex() взаимодействуют с этим общим объектом ОС, обеспечивая межпроцессную синхронизацию.

Пример использования именованного Mutex для межпроцессной синхронизации:

// Процесс A: Создает или открывает именованный мьютекс и захватывает его.
using (var mutex = new Mutex(initiallyOwned: true, name: "Global\MySingletonAppMutex"))
{
    Console.WriteLine("Процесс A владеет мьютексом. Нажмите Enter для освобождения.");
    Console.ReadLine();
    mutex.ReleaseMutex();
}

// Процесс B: Открывает существующий мьютекс и пытается его захватить.
bool createdNew;
using (var mutex = new Mutex(initiallyOwned: false, name: "Global\MySingletonAppMutex", out createdNew))
{
    Console.WriteLine($"Процесс B: Мьютекс создан заново? {createdNew}");
    // Ожидаем, пока Процесс A освободит мьютекс.
    Console.WriteLine("Процесс B ждет мьютекс...");
    if (mutex.WaitOne(TimeSpan.FromSeconds(10)))
    {
        Console.WriteLine("Процесс B захватил мьютекс.");
        mutex.ReleaseMutex();
    }
}

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

  • Префикс Global\ в Windows делает мьютекс видимым для всех сессий (полезно для служб).
  • Необходимо аккуратно обрабатывать исключения и всегда освобождать мьютекс в finally или с помощью using.
  • Межпроцессные мьютексы медленнее, чем внутрипроцессные блокировки, из-за переключения в режим ядра.

Ответ 18+ 🔞

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

В чём, собственно, разница, если по-простому:

  • lock (он же Monitor): Это как дверь в твоей квартире. Только те, кто внутри квартиры (одного процесса и домена приложения), могут за неё дёргаться и говорить "занято!". Соседи с другого этажа (другой процесс) про неё нихуя не знают.
  • Mutex: А это уже как дверь в подъезд, общая на всех. Её создали и повесили табличку с именем в самом подъезде (в пространстве имён ядра ОС). Любой жилец (процесс), который знает название этой двери, может подойти, попробовать открыть и постоять в очереди, если она занята. Вот это и есть межпроцессная синхронизация, ёпта!

Как эта магия работает:

  1. Ты создаёшь именованный Mutex — операционка тут же лепит его в своих внутренних списках (типа GlobalMySuperMutex).
  2. Любой другой процесс, который знает это волшебное имя, может найти этот же самый объект и сказать: "А можно и мне ручку подержать?".
  3. Ну а дальше всё просто: WaitOne() — это "жду у двери, пока не освободится", а ReleaseMutex() — это "всё, я вышел, следующий заходи". И так по кругу, хоть двадцать процессов.

Смотри, как это в коде выглядит, на живом примере:

// Допустим, это Процесс 1. Он создаёт или открывает мьютекс и сразу его хватает.
using (var mutex = new Mutex(initiallyOwned: true, name: "Global\MySingletonAppMutex"))
{
    Console.WriteLine("Процесс 1 владеет мьютексом. Сидит, чай пьёт. Нажми Enter, чтобы он отпустил.");
    Console.ReadLine();
    mutex.ReleaseMutex(); // Всё, выпустил из рук.
}

// А это Процесс 2. Он пытается открыть ТОТ ЖЕ САМЫЙ мьютекс по имени.
bool createdNew;
using (var mutex = new Mutex(initiallyOwned: false, name: "Global\MySingletonAppMutex", out createdNew))
{
    Console.WriteLine($"Процесс 2: Мьютекс пришлось создавать с нуля? {createdNew} (скорее всего, false)");
    // Терпеливо ждём, пока первый процесс не перестанет быть жадиной.
    Console.WriteLine("Процесс 2 ждёт мьютекс, как дурак...");
    if (mutex.WaitOne(TimeSpan.FromSeconds(10)))
    {
        Console.WriteLine("Ура! Процесс 2 наконец-то захватил мьютекс!");
        mutex.ReleaseMutex();
    }
}

А теперь, блядь, важные нюансы, чтобы не обосраться:

  • Этот префикс Global\ в винде — это чтобы мьютекс был виден вообще всем, даже между сессиями. Для служб — самое то. Без него он может быть только в пределах текущей сессии пользователя.
  • С этим добром надо обращаться аккуратно, как с гранатой. Всегда-всегда отпускай мьютекс, желательно в finally или через using, а то повесишь намертво все остальные процессы — будет тебе веселье, ядрёна вошь.
  • И да, помни: из-за того, что каждый чих идёт через ядро ОС, это в разы медленнее, чем обычный lock внутри процесса. Так что юзай только когда реально нужно синхронизировать между разными экзешниками, а не для внутренних разборок одного приложения.