详细请看 Go sync.Mutex:正常模式与饥饿模式

核心解答

sync.Mutex 的核心实现可以概括为:一个 state 整型字段(bit 标志 + 等待者计数)配合一个信号量 sema

  1. 无竞争快路径:通过 CAS 把 state 从 0 改为“locked”,成功就直接拿到锁。
  2. 有竞争慢路径:可能先短暂自旋(减少睡眠/唤醒开销),抢不到就把自己加入等待队列并 Semacquire 挂起,等待 Unlock 唤醒或“交接”。
  3. 两种模式
    • 正常模式(偏吞吐):允许新来的 goroutine “抢锁/插队”,等待者可能出现长尾等待。
    • 饥饿模式(偏公平):锁倾向于直接交接给队首等待者,限制长时间饥饿。

关于 Go 1.18 前后差异Mutex 的这套“state + sema、正常模式/饥饿模式、自旋 + 挂起/唤醒”的核心框架在 1.18 前后保持一致;面试中更重要的是把这套机制讲清楚,而不是死背某个版本的微调细节。

解答思路

  1. 先讲数据结构state 负责记录“锁是否被持有、是否已经唤醒过 waiter、是否进入饥饿模式、等待者数量”,sema 负责真正的阻塞/唤醒。
  2. 再讲两条路径:无竞争用 CAS 快路径;有竞争走“自旋 → 入队 → 信号量挂起 → 被唤醒后重试”的慢路径。
  3. 最后讲取舍:正常模式吞吐更好但可能饿死;饥饿模式更公平但吞吐可能下降(handoff 与调度切换开销)。

深度解析与面试技巧

面试官追问方向
  • 为什么要自旋?自旋多久?:自旋能减少一次睡眠/唤醒的系统开销,但自旋过度会浪费 CPU;Go 会根据竞争程度与运行环境决定是否自旋及自旋次数。
  • woken 位的意义:避免同一轮解锁唤醒多个 waiter 造成惊群和无谓竞争。
  • “公平 vs 吞吐”怎么取舍?:正常模式偏吞吐,饥饿模式偏公平;设计目标是大多数场景吞吐优先,极端长尾时切到公平止血。