← 返回首页

Go中 mutex有几种模式?

在 Go 语言(sync.Mutex)的实现中,为了在高性能(吞吐量)和公平性(防止饿死)之间取得平衡,设计了 两种运行模式:

  1. 正常模式 (Normal Mode):默认模式,追求高吞吐量。
  2. 饥饿模式 (Starvation Mode):当竞争过于激烈时自动切换,追求公平性,防止某些协程长时间获取不到锁。

这两种模式是动态切换的,不需要开发者手动配置。

1. 正常模式 (Normal Mode) —— 默认状态

  • 核心策略:“插队”竞争。
  • 工作流程:当一个 Goroutine (G) 尝试获取锁失败时,它不会立即阻塞,而是进入自旋 (Spinning) 状态(最多自旋 4 次),不断尝试通过 CAS 操作抢锁。
    • 如果自旋失败,G 会被放入等待队列尾部,并进入休眠。
    • 关键点:当锁被释放时,唤醒队列头部的 G。但是,此时如果有新的 G 刚好也来抢锁,新来的 G 和刚被唤醒的 G 一起竞争。
    • 由于新来的 G 已经在 CPU 上运行(处于活跃状态),而被唤醒的 G 需要经历上下文切换(从休眠到运行),新来的 G 抢锁成功的概率更大。
  • 优点:减少了上下文切换,吞吐量极高。
  • 缺点:不公平。如果持续有高并发请求,等待队列中的 G 可能一直抢不过新来的 G,导致“饿死” (Starvation)。

2. 饥饿模式 (Starvation Mode) —— 保护机制

  • 触发条件:当某个 G 在等待队列中阻塞时间超过 1ms(阈值 starvationThreshold),Go 运行时会将 Mutex 切换到饥饿模式。
  • 核心策略:“先进先出” (FIFO)。
  • 工作流程:锁的持有者释放锁时,直接 handing over (移交) 给等待队列头部的 G,而不是释放锁让大家竞争。
    • 新来的 G 如果发现锁处于饥饿模式,或者发现有人在等待,会直接加入等待队列尾部,不参与抢锁。
    • 新来的 G 甚至不会自旋,直接休眠。
  • 优点:绝对公平,保证了等待最久的 G 一定能拿到锁,解决了饿死问题。
  • 缺点:吞吐量下降。因为每次释放锁都涉及上下文切换(唤醒特定的 G),且新来的 G 无法利用 CPU 时间片立即执行。