应优先使用 Monitor 而非 Mutex 实现进程内同步,因 Monitor 在用户态高效完成且自动异常安全;Mutex 仅在需跨进程互斥时使用,其系统调用开销大、所有权严格、易引发死锁或异常。

别用 Mutex 替代 Monitor,除非你明确需要跨进程互斥——否则就是凭空引入内核开销和死锁风险。
Monitor.Enter 和 lock 本质是同一回事
你写的 lock (_gate) { ... } 在编译后几乎等价于:Monitor.Enter(_gate) + try/finally + Monitor.Exit(_gate)。这不是“近似”,而是 C# 编译器的硬编码行为。
这意味着:
-
Monitor.TryEnter(_gate, timeout)是唯一能加超时的替代方案,lock本身不支持超时 - 同一个线程重复进入同一个
_gate不会死锁(可重入),但Monitor默认也不支持可重入——除非你用new object()作为锁对象(.NET 会自动处理) - 所有基于
Monitor的操作都只在当前进程内有效;换一个进程,哪怕对象地址一样,也完全不感知
Mutex 必须显式命名才能跨进程
没名字的 Mutex(如 new Mutex())和 Monitor 一样,只在当前进程内起作用。真正区分它和 Monitor 的,是带名字的构造方式:
bool createdNew;
using (var mutex = new Mutex(true, "Global\MyAppSingleInstance", out createdNew))
{
if (!createdNew)
{
// 另一个进程已持有该命名 Mutex
Console.WriteLine("程序已在运行");
return;
}
// 正常启动逻辑
}注意点:
- 名字前缀
Global\或Local\决定作用域:Windows 上Global\可被所有会话访问,Local\仅限当前会话 -
createdNew是唯一可靠的判断依据,不要依赖WaitOne()返回值做单实例判断——它可能因权限或句柄泄漏失败 - 必须调用
ReleaseMutex(),且只能由获取者释放;忘记释放会导致其他进程永久阻塞
性能差距不是“稍微慢一点”,而是量级差异
Monitor 大部分时间在用户态完成(比如自旋+轻量等待),而 Mutex 每次 WaitOne() / ReleaseMutex() 都触发一次系统调用(Windows 上是 NtWaitForSingleObject 和 NtReleaseMutant)。
实测对比(100 万次锁获取/释放):
-
Monitor.Enter/Exit:约 80–120ms -
Mutex.WaitOne/ReleaseMutex(无竞争):约 1200–1800ms - 有竞争时,
Mutex还会额外触发线程调度、上下文切换,延迟跳升到毫秒级
所以如果你只是保护一个 Dictionary<string, int> 的读写,用 Mutex 就像用起重机拧螺丝——能动,但没必要,还震得慌。
最容易被忽略的坑:Mutex 的所有权模型
Mutex 是严格的所有权机制:谁 WaitOne() 成功,谁就必须 ReleaseMutex()。这带来两个反直觉问题:
- 如果线程在持有
Mutex时异常退出且没finally释放,该Mutex会进入“废弃”状态(abandoned),下个获取者会收到AbandonedMutexException,而不是直接拿到锁 - 不能跨线程释放——主线程拿的锁,子线程不能替它释放;否则抛
UnauthorizedAccessException -
using块只保证Dispose(),不等于ReleaseMutex();必须显式调用,且确保只调一次
相比之下,Monitor 的 lock 语句块哪怕抛异常,finally 也会强制 Exit,安全边界更清晰。


















