请详细阐述 C# 中 Monitor 和 Mutex 等多线程同步原语的底层实现机制。分别说明它们在进入、等待和退出临界区时,是如何基于操作系统内核对象或用户态构造来完成的?
考察说明
考查对 C# 同步原语底层实现原理的理解,区分用户态与内核态同步机制。
回答思路
- 【回答框架 1】Monitor 基于 .NET 运行时提供的同步块(SyncBlock)实现,每个对象可能关联一个同步块索引,初始时对象头中的索引指向空,当调用 Monitor.Enter 时,如果对象未关联同步块,则分配一个索引并挂接,然后通过原子操作尝试获取锁,若锁被占用则线程进入等待队列,通常是阻塞线程(涉及内核事件),Monitor 支持可重入,同一线程可多次 Enter,内部记录递归计数。
- 【回答框架 2】Mutex 是封装了操作系统内核对象的同步原语,通常对应 Windows 内核互斥体,创建时指定名称可实现跨进程同步。线程等待 Mutex 时直接进入内核等待状态,由内核调度器管理等待队列,因此性能开销比 Monitor 高,但支持跨进程;Mutex 也支持可重入,内部记录线程所有权和递归计数,但所有权不由运行时维护,而是由内核对象跟踪。
- 【回答框架 3】底层操作包括原子指令(如 Interlocked.CompareExchange)用于无锁状态检查,以及线程阻塞与唤醒(内核事件或信号量)。Monitor 还有 TryEnter 等非阻塞尝试,而 lock 语句是 Monitor.Enter/Exit 的语法糖,内部使用 try-finally 确保 Exit。
- 【关键点 1】Monitor 主要基于用户态同步块和对象头索引,等待时可能升级为内核阻塞。
- 【关键点 2】Mutex 基于内核对象,支持跨进程同步,但性能开销更大。
- 【关键点 3】两者都支持可重入,通过所有权和递归计数实现。
- 【易错点 1】误以为 Monitor 一定不涉及内核切换,实际上阻塞时也依赖内核同步。
- 【易错点 2】混淆 Monitor 与 lock 语句,lock 只是简化写法,底层仍是 Monitor。
- 【易错点 3】误认为 Mutex 可以在进程内比 Monitor 更快,实际通常更慢。