C#面试题更新 2026-08-05

请从底层实现的角度解释 C# 中 lock 语句以及 C# 提供的其他同步原语(如 Monitor、Mutex、Semaphore、Interlocked 等)是如何工作的?

技术原理C#

考察说明

考察对 C# 同步原语底层实现机制的理解深度,特别是 lock 与 Monitor 的关系及操作系统层面的支持。

回答思路

  1. 【回答框架 1】lock 语句在 C# 中是 Monitor 的语法糖,其底层通过 System.Threading.Monitor 类实现。Monitor 使用内核对象(如事件)和用户态的自旋锁(SpinLock)结合,进入 lock 时会先尝试获取用户态锁,若失败则阻塞并进入内核态等待,以避免频繁的上下文切换。
  2. 【回答框架 2】Monitor.Enter 实际调用的是 CLR 内部的 SyncBlock 机制,每个对象都有与之关联的 SyncBlock 索引(通常为 0 表示无锁),通过 Interlocked 操作(如 CompareExchange)来更新对象头中的索引,从而标记锁的持有状态。若竞争激烈,则使用操作系统提供的等待队列(如 WaitHandle)来挂起线程。
  3. 【回答框架 3】其他同步原语各有侧重:Mutex 是跨进程同步的内核对象,每次 WaitOne/ReleaseMutex 都会进入内核态,性能开销大;Semaphore 也是内核对象,支持计数限流;Interlocked 类提供原子操作(如 Increment、Exchange),基于 CPU 的原子指令(如 x86 的 lock 前缀)实现,不涉及内核切换,性能最高。
  4. 【回答框架 4】自旋锁(SpinLock)在用户态循环等待,避免上下文切换,适用于临界区极短且多核场景;而 lock(Monitor)在用户态自旋一定时间后仍无法获得锁时会转为内核等待,兼顾了短临界区的效率和长等待的公平性。
  5. 【回答框架 5】在 .NET 中,lock 块中的代码在重新编译后由编译器生成 Monitor.Enter(带 try/finally 保证释放)的调用。底层还涉及内存屏障(Memory Barrier),确保锁内的读写操作不被重排序,保证可见性。
  6. 【关键点 1】lock 是 Monitor 的语法糖,Monitor 利用 SyncBlock 索引和 Interlocked 实现用户态快速路径。
  7. 【关键点 2】用户态自旋失败后,Monitor 会切换到内核态等待,避免 CPU 空转。
  8. 【关键点 3】Mutex 和 Semaphore 是内核对象,开销大,但支持跨进程同步;Interlocked 基于原子指令,开销最小。
  9. 【关键点 4】底层通过内存屏障保证锁内操作的可见性和有序性。
  10. 【关键点 5】SpinLock 适用于短临界区,可避免上下文切换,但长时间自旋会浪费 CPU。
  11. 【易错点 1】不要将 lock 与业务幂等性直接等同,lock 只保证互斥,无法保证业务结果的唯一性。
  12. 【易错点 2】线程数估算公式 Ncpu × (1 + W/C) 只是初始值,需以实测为准。
  13. 【易错点 3】自旋锁在单核 CPU 上可能因无法让出 CPU 而导致死锁,需谨慎使用。