0

0

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

畫卷琴夢

畫卷琴夢

发布时间:2026-01-03 10:49:02

|

910人浏览过

|

来源于php中文网

原创

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

c# monitor.wait 和 monitor.pulse/pulseall 的用法和原理

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

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

  • Wait() 必须在已持锁状态下调用,否则抛 SynchronizationLockException
  • Pulse()PulseAll() 同样必须由当前持锁线程调用,否则无效或抛异常
  • 如果 Pulse() 调用时等待队列为空,信号就丢失——下个调用 Wait() 的线程会一直卡住,除非超时
  • 没有“自动重试”机制:线程从 Wait() 返回后,必须手动重新检查条件是否满足(典型模式:while 循环包裹 Wait)

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

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

lock (syncObj)
{
    while (!conditionIsMet) // ⚠️ 必须用 while!
    {
        Monitor.Wait(syncObj);
    }
    // 此时 conditionIsMet 为 true,安全操作
}
  • 虚假唤醒虽在 .NET 中极少见,但规范要求必须防御;更常见的是:Pulse 后多个线程被唤醒(尤其用了 PulseAll),但只有一个能真正处理,其余必须继续等
  • 条件检查必须在 lock 内完成,否则存在竞态:检查完、Wait 前条件又被其他线程改回 false
  • Monitor.Wait(syncObj, timeout) 返回 false 表示超时,此时仍需检查条件是否满足,不能默认失败

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

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

95Shop仿醉品商城
95Shop仿醉品商城

95Shop可以免费下载使用,是一款仿醉品商城网店系统,内置SEO优化,具有模块丰富、管理简洁直观,操作易用等特点,系统功能完整,运行速度较快,采用ASP.NET(C#)技术开发,配合SQL Serve2000数据库存储数据,运行环境为微软ASP.NET 2.0。95Shop官方网站定期开发新功能和维护升级。可以放心使用! 安装运行方法 1、下载软件压缩包; 2、将下载的软件压缩包解压缩,得到we

下载
  • Pulse():适用于“单消费者-单生产者”或“一次只允许一个线程推进”的场景(如队列取一个任务、信号量减一)
  • PulseAll():适用于“状态全局变化,所有等待者都该重新评估”的场景(如缓存刷新完成、批量任务结束、取消标志置位)
  • ⚠️ 注意:PulseAll() 可能引发惊群效应(thundering herd)——大量线程同时争锁,造成短暂 CPU 尖峰;若等待线程多且条件大多不满足,建议改用更细粒度的同步原语(如 BlockingCollectionSemaphoreSlim

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

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

  • 无法与 async/await 混用:Wait() 会阻塞线程,而 await 期望释放线程——二者语义冲突;需要异步等待,请用 SemaphoreSlim.WaitAsync()TaskCompletionSource
  • 死锁风险高:比如 A 线程 Wait 后没被 Pulse,B 线程 Pulse 后自己又 Wait 且没被唤醒,互相卡住
  • 调试困难:VS 的“并行堆”窗口能看到线程卡在 Monitor.Wait,但看不到“谁该 Pulse”——必须靠日志或断点确认 Pulse 是否在正确时机、由正确线程发出
  • 现代替代方案优先级:BlockingCollection(生产者-消费者)、Channel(高吞吐流)、ManualResetEventSlim(简单信号)通常比裸 Monitor 更安全、易读
真实项目里,Monitor.Wait/Pulse 应该是“你已经试过所有高级封装、确认它们不合适之后”的最后手段。它的原理清晰,但每一步都要求你对线程调度、队列状态和条件竞争有精确控制——稍有疏忽,问题就藏得深、复现难。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

713

2023.08.22

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

82

2023.09.25

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

375

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

564

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

375

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

564

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

473

2023.08.10

Golang channel原理
Golang channel原理

本专题整合了Golang channel通信相关介绍,阅读专题下面的文章了解更多详细内容。

241

2025.11.14

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

150

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 0.9万人学习

进程与SOCKET
进程与SOCKET

共6课时 | 0.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号