贝利信息

c# Monitor.Wait 和 Monitor.Pulse/PulseAll 的用法和原理

日期:2026-01-03 00:00 / 作者:畫卷琴夢
Wait 和 Pulse 实际是“通知+排队+抢锁”三步:Pulse 将等待队列首线程移至就绪队列,当前线程释放锁后,就绪线程竞争锁成功才从 Wait 返回;必须用 while 循环检查条件,且 Wait/Pulse 均需在持锁状态下调用。

Wait 和 Pulse 不是“唤醒就执行”,而是“通知+排队+抢锁”三步走

很多人以为 Monitor.Pulse() 一调用,等待的线程就立刻继续运行——这是最大误解。实际流程是:持有锁的线程调用 Pulse → 等待队列头部线程被移到就绪队列 → 当前线程释放锁(比如退出 lock 块)→ 就绪队列中某个线程(不一定是刚被 Pulse 的那个)抢到锁 → 它才真正从 Wait() 返回并继续执行。

正确写法:永远用 while + Wait,别用 if

这是生产环境最常踩的坑。用 if 判断条件后直接 Wait(),会导致虚假唤醒(spurious wakeup)或条件变更后误执行。正确模式是:

lock (syncObj)
{
    while (!conditionIsMet) // ⚠️ 必须用 while!
    {
        Monitor.Wait(syncObj);
    }
    // 此时 conditionIsMet 为 true,安全操作
}

Pulse vs PulseAll:选哪个取决于“通知范围”

Monitor.Pulse() 只唤醒等待队列**头部一个线程**;Monitor.PulseAll() 唤醒**所有等待线程**。选择依据不是“哪个更快”,而是语义:

比 lock 更重,比 async/await 更难调试

Monitor.Wait/Pulse 是纯同步、阻塞式协调,它和 lock 共享同一套 CLR 同步块机制,但复杂度指数上升:

真实项目里,Monitor.Wait/Pulse 应该是“你已经试过所有高级封装、确认它们不合适之后”的最后手段。它的原理清晰,但每一步都要求你对线程调度、队列状态和条件竞争有精确控制——稍有疏忽,问题就藏得深、复现难。