c# monitor 和 lock 有什么区别

12次阅读

lock是Monitor的语法糖,编译后完全等价于Monitor.Enter+try-finally+Monitor.Exit;它自动确保异常时释放锁,但不支持超时、Wait/Pulse等高级功能。

c# monitor 和 lock 有什么区别

lock 就是 Monitor 的语法糖,别把它当两个东西学

你写 lock(obj) { ... },编译器立刻把它翻译成 Monitor.Enter + try-finally + Monitor.Exit。不是“类似”,是**完全等价**——IL 层面一模一样。所以别纠结“哪个更底层”,lock 不是替代品,它是 Monitor 的安全封装

  • 编译后自动加 try-finally,哪怕临界区抛异常,锁也必释放(不用你操心)
  • 不支持超时、等待、唤醒——这些功能根本没暴露出来
  • 只接受引用类型作为锁对象;传值类型会隐式装箱,每次装箱生成新对象,导致锁失效甚至死锁

Monitor.TryEnter 能帮你躲开死锁,lock 做不到

当你不确定锁会不会被长时间占用(比如依赖外部服务、数据库慢查询、或另一个可能卡住的线程),用 Monitor.TryEnter(obj, timeout) 是唯一靠谱的选择。它返回 bool:成功拿到锁就干正事,失败就走降级逻辑,而不是傻等。

  • timeout 单位是毫秒,传 0 表示“试试看,不等”;传 -1 等价于无限等待(和 Monitor.Enter 一样)
  • 必须配对调用 Monitor.Exit,且只能在 lockTaken == true 时调用,否则抛 SynchronizationLockException
  • 常见错误:忘了在 finally 里检查 lockTaken,直接 Monitor.Exit → 运行时报错
bool lockTaken = false; try {     if (Monitor.TryEnter(_syncObj, 500)) // 等最多 500ms     {         lockTaken = true;         // 执行临界区     }     else     {         Log.Warn("获取锁超时,跳过处理");         return;     } } finally {     if (lockTaken) Monitor.Exit(_syncObj); }

Wait/Pulse 只能在 Monitor 里用,lock 完全没这能力

如果你在写生产者-消费者、信号量控制、状态驱动的协同逻辑(比如“等数据来了再处理”),lock 直接出局。Monitor.Wait 会**主动释放锁并挂起当前线程**,直到另一个线程调用 Monitor.PulseMonitor.PulseAll 唤醒它——这是协作式同步的核心机制。

  • Wait 必须在已持有锁的前提下调用,否则抛 SynchronizationLockException
  • Pulse 只唤醒一个等待线程;PulseAll 唤醒全部——但唤醒不等于立即执行,它们还得重新竞争锁
  • 典型陷阱:先 PulseWait(即“信号丢失”),必须用循环条件检查 + while 包裹 Wait

锁对象选错,lock 和 Monitor 都会翻车

无论用哪个,锁对象本身出问题,同步就形同虚设。最常见三类雷:

  • this:外部代码可能也锁你实例,引发意外阻塞
  • typeof(MyClass)字符串字面量:跨 Assembly 或 intern 字符串共享,锁范围远超预期
  • public 字段或可变对象(比如 public Object SyncRoot):别人改了它,你的 Monitor.Exit 就找不到原锁对象

正确做法永远是:private readonly object _syncObj = new object(); —— 它只属于你,不可变,不暴露。

Monitor 提供能力,lock 提供安全底线;该用哪个,不看“高级感”,而看有没有 Wait/Pulse 或超时需求。其余时候,老老实实写 lock,少一行代码,少一个 bug

text=ZqhQzanResources