Ответ
Mutex (взаимное исключение) может синхронизировать потоки из разных процессов, потому что это объект уровня операционной системы, а не среды выполнения (CLR).
Ключевые отличия от lock/Monitor:
lock(Monitor): Работает только в пределах одного процесса и домена приложения (AppDomain), используя память кучи CLR.Mutex: Создается в глобальном пространстве имен ядра ОС (например, в Windows). Разные процессы могут открыть хэндл одного и того же именованного объекта ядра.
Как это работает:
- При создании именованного
MutexОС регистрирует его в своем внутреннем пространстве имен (например,Global\MyAppMutex). - Любой процесс, знающий это имя, может открыть хэндл на тот же самый объект ядра.
- Операции
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: А это уже как дверь в подъезд, общая на всех. Её создали и повесили табличку с именем в самом подъезде (в пространстве имён ядра ОС). Любой жилец (процесс), который знает название этой двери, может подойти, попробовать открыть и постоять в очереди, если она занята. Вот это и есть межпроцессная синхронизация, ёпта!
Как эта магия работает:
- Ты создаёшь именованный
Mutex— операционка тут же лепит его в своих внутренних списках (типаGlobalMySuperMutex). - Любой другой процесс, который знает это волшебное имя, может найти этот же самый объект и сказать: "А можно и мне ручку подержать?".
- Ну а дальше всё просто:
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внутри процесса. Так что юзай только когда реально нужно синхронизировать между разными экзешниками, а не для внутренних разборок одного приложения.