当前位置: 首页 > news >正文

多线程六脉神剑第二剑:监视器锁 (Monitor)

文章目录

  • 1、举栗子🌰
  • 2、Monitor 的本质
    • 2.1 对象头中的秘密
    • 2.2 对象与锁的关联
  • 3、核心工作原理
    • 3.1 锁的获取流程
    • 3.2 锁的释放流程
  • 4、Monitor 的完整使用
    • 4.1 基础用法 - lock 语法糖
    • 4.2 高级功能 - Wait/Pulse 机制
    • 4.3 超时控制 - TryEnter
  • 5、Wait/Pulse 的详细机制
    • 5.1 等待队列的工作原理
    • 5.2 复杂的协调示例
  • 6、Monitor 的底层优化
    • 6.1 瘦锁(Thin Lock)
    • 6.2 自旋等待
  • 7、最佳实践和陷阱
    • 7.1 正确做法
    • 7.2 常见陷阱
  • 8、总结

1、举栗子🌰

场景:公司的唯一打印机

打印机是共享资源

  • Monitor.Enter() 相当于 拿到打印权限并关门

  • Monitor.Exit() 相当于 打印完成并开门

  • Monitor.Wait() 相当于 暂时离开但保留排队位置

  • Monitor.Pulse() 相当于 通知下一个人可以准备了

2、Monitor 的本质

2.1 对象头中的秘密

每个 .NET 对象内部都有一个对象头,其中包含同步块索引:

// 概念性结构 - 实际在 CLR 中实现
class ObjectHeader 
{SyncBlockIndex syncBlock;  // 同步块索引TypeHandle typeHandle;     // 类型信息// ... 其他字段
}

同步块(SyncBlock) 才是真正的锁信息容器:

class SyncBlock 
{Thread ownerThread;        // 当前拥有锁的线程int recursionCount;        // 重入计数WaitQueue waitQueue;       // 等待队列// ... 其他同步信息
}

2.2 对象与锁的关联

object lockObj = new object(); // 任何引用类型对象都可以作为锁// 当你执行 lock(lockObj) 时:
// 1. 检查 lockObj 的对象头中的同步块索引
// 2. 如果为0,创建新的同步块并关联
// 3. 如果已有同步块,检查锁状态
// 4. 根据情况获取锁或进入等待

3、核心工作原理

3.1 锁的获取流程

// 伪代码展示 Monitor.Enter 的内部逻辑
bool MonitorEnter(object obj, ref bool lockTaken)
{// 第一步:快速路径 - 无竞争情况if (obj.SyncBlockIndex == 0) {// 使用原子操作设置同步块if (Interlocked.CompareExchange(ref obj.SyncBlockIndex, CreateSyncBlock(), 0) == 0){obj.SyncBlock.OwnerThread = CurrentThread;obj.SyncBlock.RecursionCount = 1;lockTaken = true;return true;}}// 第二步:检查是否已由当前线程持有(递归锁)if (obj.SyncBlock.OwnerThread == CurrentThread){obj.SyncBlock.RecursionCount++;lockTaken = true;return true;}// 第三步:慢速路径 - 需要等待return SlowPathMonitorEnter(obj, ref lockTaken);
}

3.2 锁的释放流程

void MonitorExit(object obj)
{// 验证当前线程确实持有锁if (obj.SyncBlock.OwnerThread != CurrentThread)throw new SynchronizationLockException();// 减少递归计数obj.SyncBlock.RecursionCount--;if (obj.SyncBlock.RecursionCount == 0){// 真正释放锁obj.SyncBlock.OwnerThread = null;// 如果有等待线程,唤醒一个if (obj.SyncBlock.WaitQueue.Count > 0){WakeOneWaitingThread(obj);}}
}

4、Monitor 的完整使用

4.1 基础用法 - lock 语法糖

class BasicUsage
{private object _lockObject = new object();private int _sharedCounter = 0;// 方式1:使用 lock 关键字(推荐)void IncrementWithLock(){lock (_lockObject)  // 编译器自动生成 Monitor.Enter/Exit{_sharedCounter++;// 其他临界区代码}}// 方式2:显式使用 Monitor(等价于上面的 lock)void IncrementWithMonitor(){bool lockTaken = false;try{Monitor.Enter(_lockObject, ref lockTaken);_sharedCounter++;}finally{if (lockTaken){Monitor.Exit(_lockObject);}}}
}

为什么使用 ref bool lockTaken?

// 防止异步异常导致锁无法释放
void DangerousMethod()
{Monitor.Enter(_lockObject); // 不推荐!如果这里发生异常,锁不会被释放try{// 临界区}finally{Monitor.Exit(_lockObject);}
}void SafeMethod()
{bool lockTaken = false;try{Monitor.Enter(_lockObject, ref lockTaken); // 原子操作,要么完全成功要么完全失败// 临界区}finally{if (lockTaken) // 只有成功获取锁才释放{Monitor.Exit(_lockObject);}}
}

4.2 高级功能 - Wait/Pulse 机制

这是 Monitor 最强大的特性,用于复杂的线程协调:

class ProducerConsumerWithMonitor
{private object _lockObject = new object();private Queue<string> _queue = new Queue<string>();private bool _isStopped = false;// 生产者public void Produce(string item){lock (_lockObject){// 如果队列太大,等待消费者处理while (_queue.Count >= 5){Console.WriteLine("生产者: 队列已满,等待...");Monitor.Wait(_lockObject); // 释放锁并等待}_queue.Enqueue(item);Console.WriteLine($"生产者: 添加 {item},队列大小: {_queue.Count}");// 通知等待的消费者Monitor.Pulse(_lockObject);}}// 消费者public string Consume(){lock (_lockObject){// 如果队列为空,等待生产者while (_queue.Count == 0 && !_isStopped){Console.WriteLine("消费者: 队列为空,等待...");Monitor.Wait(_lockObject);}if (_queue.Count == 0) return null; // 已停止且队列为空string item = _queue.Dequeue();Console.WriteLine($"消费者: 取出 {item},队列大小: {_queue.Count}");// 通知等待的生产者Monitor.Pulse(_lockObject);return item;}}public void Stop(){lock (_lockObject){_isStopped = true;Monitor.PulseAll(_lockObject); // 唤醒所有等待线程}}
}// 使用示例
class Program
{static void Main(){var pc = new ProducerConsumerWithMonitor();// 启动生产者线程Thread producer = new Thread(() => {for (int i = 0; i < 10; i++){pc.Produce($"产品-{i}");Thread.Sleep(100);}});// 启动消费者线程Thread consumer = new Thread(() => {for (int i = 0; i < 10; i++){pc.Consume();Thread.Sleep(150);}});producer.Start();consumer.Start();producer.Join();consumer.Join();}
}

4.3 超时控制 - TryEnter

class TimeoutExample
{private object _lockObject = new object();public bool TryDoWork(int timeoutMs){bool lockTaken = false;try{// 尝试在指定时间内获取锁lockTaken = Monitor.TryEnter(_lockObject, timeoutMs);if (lockTaken){// 成功获取锁,执行工作DoCriticalWork();return true;}else{// 超时,执行备用方案Console.WriteLine($"获取锁超时 ({timeoutMs}ms),执行备用操作");DoFallbackWork();return false;}}finally{if (lockTaken){Monitor.Exit(_lockObject);}}}private void DoCriticalWork(){Thread.Sleep(2000); // 模拟耗时工作Console.WriteLine("关键工作完成");}private void DoFallbackWork(){Console.WriteLine("执行备用工作");}
}

5、Wait/Pulse 的详细机制

5.1 等待队列的工作原理

class WaitPulseMechanism
{private object _lockObject = new object();void DemonstrateWaitPulse(){lock (_lockObject){// 当调用 Monitor.Wait(_lockObject) 时:// 1. 当前线程释放 _lockObject 的锁// 2. 线程进入 _lockObject 的等待队列// 3. 线程状态改为 WaitSleepJoin// 当其他线程调用 Monitor.Pulse(_lockObject) 时:// 1. 从等待队列中移出一个线程// 2. 该线程进入就绪队列,准备重新获取锁// 当其他线程调用 Monitor.PulseAll(_lockObject) 时:// 1. 所有等待线程都移到就绪队列}}
}

5.2 复杂的协调示例

class ComplexCoordination
{private object _lockObject = new object();private int _currentPhase = 0;private int _threadsReady = 0;private const int TOTAL_THREADS = 3;public void WorkerThread(int threadId){for (int phase = 0; phase < 3; phase++){lock (_lockObject){// 等待进入下一阶段while (_currentPhase != phase){Monitor.Wait(_lockObject);}_threadsReady++;Console.WriteLine($"线程 {threadId} 准备就绪阶段 {phase}");// 如果所有线程都就绪,推进到下一阶段if (_threadsReady == TOTAL_THREADS){_currentPhase++;_threadsReady = 0;Console.WriteLine($"=== 所有线程完成阶段 {phase},进入阶段 {_currentPhase} ===");Monitor.PulseAll(_lockObject); // 唤醒所有线程}else{// 等待其他线程while (_currentPhase == phase){Monitor.Wait(_lockObject);}}}// 执行阶段工作Console.WriteLine($"线程 {threadId} 执行阶段 {phase} 的工作");Thread.Sleep(500);}}
}

6、Monitor 的底层优化

6.1 瘦锁(Thin Lock)

对于轻度竞争的情况,CLR 使用优化:

// 概念性优化
class ThinLock
{// 对于轻度使用,直接在对象头中存储锁信息,避免创建完整的SyncBlock// 使用位域存储:线程ID + 递归计数// 当竞争激烈时,才升级为完整的SyncBlock
}

6.2 自旋等待

在进入内核等待前,会先自旋一段时间:

bool TryEnterWithSpin(object obj, int timeoutMs)
{int spinCount = CalculateOptimalSpinCount();for (int i = 0; i < spinCount; i++){if (TryEnterFastPath(obj)) return true;Thread.SpinWait(100); // 用户态自旋,避免内核切换}return EnterSlowPath(obj, timeoutMs);
}

7、最佳实践和陷阱

7.1 正确做法

class BestPractices
{private readonly object _lockObject = new object(); // readonly 防止意外修改public void GoodPractice1(){lock (_lockObject){// 临界区代码尽量简短DoQuickOperation();}// 长时间操作放在锁外DoLongRunningOperation();}public void GoodPractice2(){// 使用明确的超时控制if (Monitor.TryEnter(_lockObject, 1000)){try{// 工作}finally{Monitor.Exit(_lockObject);}}}
}

7.2 常见陷阱

class CommonMistakes
{private object _lockObject = new object();// 错误1:锁住错误的对象public void Mistake1(){lock (this) // 不好!外部代码可能也锁住这个实例{// ...}}// 错误2:在锁内调用未知方法public void Mistake2(){lock (_lockObject){SomeExternalMethod(); // 危险!可能造成死锁}}// 错误3:忘记释放锁public void Mistake3(){Monitor.Enter(_lockObject); // 如果没有对应的Exit,会导致死锁// 应该使用 try-finally 或 lock 关键字}// 错误4:错误的 Wait/Pulse 使用public void Mistake4(){// 必须在锁内调用 Wait/PulseMonitor.Wait(_lockObject); // 错误!不在锁内}
}

8、总结

Monitor 的本质:

  • 是基于对象头同步块的软件锁

  • 提供递归获取能力(同一线程可多次获取)

  • 内置等待/通知机制(Wait/Pulse)

  • 在用户态和内核态间智能切换

核心优势:

  1. 与对象绑定:任何对象都可作为锁

  2. 递归安全:同一线程不会死锁自己

  3. 条件变量:内置复杂的线程协调能力

  4. 性能优化:瘦锁、自旋等待等优化

使用要点:

优先使用 lock 关键字

  • 复杂协调使用 Wait/Pulse

  • 总是用 try-finally 确保锁释放

  • 避免在锁内执行耗时操作

http://www.dtcms.com/a/519567.html

相关文章:

  • 飞书多维表格自动化做音视频文案提取,打造素材库工作流,1分钟学会
  • 基于主题聚类的聊天数据压缩与智能检索系统
  • 结构健康自动化监测在云端看数据变化,比人工更及时精准,优缺点分析?
  • 做夹具需要知道的几个网站服装页面设计的网站
  • 分享影视资源的网站怎么做网站字头优化
  • 照明回路配线-批量测量超实用
  • Python 条件判断机制本质
  • 关于spiderdemo第二题的奇思妙想
  • Python处理指定目录下文件分析操作体系化总结
  • k8s部署自动化工具jenkins
  • YOLOv5 目标检测算法详解(一)
  • No040:陪伴的艺术——当DeepSeek学会在时光中温柔在场
  • 6-1〔O҉S҉C҉P҉ ◈ 研记〕❘ 客户端攻击▸侦查客户端指纹
  • 苏州企业网站设计企业phpstudy如何建设网站
  • 仿站网站域名网站建设数据库实验心得
  • 怎么看电脑的主板BIOS型号
  • 广东省高校质量工程建设网站管理登陆网站开发软件
  • 压缩与缓存调优实战指南:从0到1根治性能瓶颈(一)
  • LeetCode 381: O(1) 时间插入、删除和获取随机元素 - 允许重复
  • 一次RedisOOM 排查
  • MongoDB迁移到KES实战全纪录(下):性能优化与实践总结
  • 【Java 开发日记】我们来讲一讲阻塞队列及其应用
  • 免费网站统计代码农业电商平台有哪些
  • 在长沙做网站需要多少钱手机网页禁止访问解除
  • IEEE754是什么?
  • [lc-rs] 树|建桥贪心
  • 状压DP:从入门到精通
  • Open-webui
  • AIDD - 前沿生物科技 自主决策实验 (Autonomous Experimentation) 的简述
  • 网络管理员教程(初级)第六版--第5章网络安全及管理