Mutex是跨进程同步原语,非线程锁;命名Mutex才可跨进程,需注意Global/Local前缀与权限;必须用try/finally确保ReleaseMutex;WaitOne可能抛AbandonedMutexException,需校验资源一致性。

Mutex 是跨进程的,不是线程锁
别把它当 lock 用。C# 的 Mutex 默认就是系统级内核对象,天生支持跨进程同步——这是它和 Monitor(即 lock)、SpinLock、ReaderWriterLockSlim 的根本区别。如果你只在单个进程里用 Mutex,性能反而比 lock 差一个数量级,因为每次都要进内核态。
创建命名 Mutex 才能跨进程
无参构造 new Mutex() 创建的是匿名互斥体,仅限当前进程内可见。要让其他进程也能打开并等待它,必须用带名称的构造函数,并注意权限和作用域:
-
Mutex名称是全局的,Windows 下区分大小写,建议全小写 + 连字符,例如"myapp-config-writer" - 若需多用户会话间共享(如服务和桌面用户进程通信),名称前加
"Global\"前缀;仅限当前会话用"Local\"(默认隐含) - 普通用户进程无法访问
Global\下的对象,除非服务以 LocalSystem 启动且显式配置了 DACL,否则会抛UnauthorizedAccessException
bool createdNew;
using (var mutex = new Mutex(false, "Local\myapp-data-sync", out createdNew))
{
if (!mutex.WaitOne(3000)) // 等待 3 秒
{
Console.WriteLine("获取锁超时");
return;
}
<pre class='brush:php;toolbar:false;'>try
{
// 临界区:读写共享文件、注册表、内存映射等
File.WriteAllText(@"C:sharedconfig.json", data);
}
finally
{
mutex.ReleaseMutex();
}}
WaitOne 返回 false 不一定失败,要检查异常和所有权
WaitOne 超时返回 false,但更危险的是抛出异常——比如另一进程已终止却未释放 Mutex,此时 .NET 会检测到“放弃”状态并抛 AbandonedMutexException。这个异常意味着临界区可能处于不一致状态,不能忽略:
- 捕获
AbandonedMutexException后,当前线程自动获得所有权,但你得先验证共享资源是否完好(比如校验文件 CRC、重载配置) -
WaitOne的millisecondsTimeout设为0表示“只试一次”,适合轮询场景;设为Timeout.Infinite会永久阻塞(慎用) - 不要依赖
createdNew输出参数判断“谁先抢到”——它只表示创建时是否新建了内核对象,而非当前调用是否获得锁
必须手动 ReleaseMutex,且不能跨线程释放
Mutex 不像 lock 那样有语法糖保障释放。忘记 ReleaseMutex() 会导致死锁:其他进程永远等不到信号。更隐蔽的问题是,在 A 线程 WaitOne 成功后,由 B 线程调用 ReleaseMutex() 会抛 ApplicationException(.NET 报 “Object synchronization method was called from an unsynchronized block of code”)。
- 务必用
try/finally或using(配合Dispose,它内部调用ReleaseMutex)包裹临界区 -
Dispose()只释放句柄,不销毁内核对象;只有最后一个持有句柄的进程调用Close()或Dispose(),系统才真正清理Mutex - 进程崩溃时系统会自动清理,但异常退出(如
Environment.FailFast)可能跳过finally,所以临界区操作本身也得有幂等或恢复机制
跨进程同步真正难的不是加锁,而是怎么定义“临界区操作”的边界和一致性——比如两个进程同时修改同一 JSON 文件,光靠 Mutex 防不了写一半崩溃。这时候得结合原子写入(重命名临时文件)、版本号或日志追加等手段。Mutex 只是那把钥匙,门后的东西得自己守好。










