【Go】Mutex

点击阅读更多查看文章内容

基本原语

Go 语言在 sync 包中提供了用于同步的一些基本原语,包括常见的 sync.Mutexsync.RWMutexsync.WaitGroupsync.Oncesync.Cond

这些基本原语提供了较为基础的同步功能,但是它们是一种相对原始的同步机制,在多数情况下,我们都应该使用抽象层级更高的 Channel 实现同步。


Mutex

Go 语言的 sync.Mutex 由两个字段 statesema 组成。其中 state 表示当前互斥锁的状态,而 sema 是用于控制锁状态的信号量。

1
2
3
4
type Mutex struct {
state int32
sema uint32
}

**state**:32位整数,按位存储锁的状态:

  • 第0位locked(是否被锁定,0=未锁,1=已锁)
  • 第1位woken(是否有 goroutine 被唤醒,0=无,1=有)
  • 第2位starving(是否处于饥饿模式,0=正常,1=饥饿)
  • 剩余29位waiterCount(等待锁的 goroutine 数量)

golang-mutex-state

在默认情况下,互斥锁的所有状态位都是 0

**sema**:信号量,用于阻塞和唤醒 goroutine(基于 runtime.semacquireruntime.semrelease

Lock() 加锁流程

(1) 快速路径(Fast Path)

  • 如果锁未被占用(state == 0),直接通过 CAS(Compare-And-Swap) 获取锁:

    1
    2
    3
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
    return // 成功获取锁
    }
  • 适用于低竞争场景,几乎无开销。

(2) 慢路径(Slow Path)

如果锁已被占用,进入 自旋 + 阻塞 逻辑:

  1. 自旋尝试(Spin):
    • 如果当前是 多核 CPU 且锁未进入 饥饿模式,goroutine 会自旋几次(约 4 次),尝试抢锁。
    • 自旋的目的是减少上下文切换开销
  2. 更新等待计数:
    • 自旋失败后,通过 atomic.AddInt32(&m.state, 1<<mutexWaiterShift) 增加 waiterCount(等待者数量)。
  3. 进入阻塞:
    • 调用 runtime.semacquire(&m.sema) 让当前 goroutine 进入 阻塞状态,等待被唤醒。
  4. 被唤醒后:
    • 如果锁处于 饥饿模式,直接获取锁(避免新来的 goroutine 抢锁)。
    • 否则,重新竞争锁。

Unlock() 解锁流程

1) 快速路径(Fast Path)

  • 如果 无等待者(waiterCount == 0),直接释放锁:

    1
    2
    3
    4
    new := atomic.AddInt32(&m.state, -mutexLocked)
    if new == 0 {
    return // 无竞争,直接返回
    }

(2) 慢路径(Slow Path)

如果有等待的 goroutine:

  1. 唤醒一个等待者:
    • 如果锁未进入 饥饿模式,唤醒最早被阻塞的 goroutine,此时新来的goroutine可能直接抢到锁。
    • 如果锁处于 饥饿模式,直接将锁交给 队首的等待者(避免新 goroutine 抢锁)。
  2. 减少等待计数:
    • 更新 waiterCount 并清除 woken 标志。

正常模式和饥饿模式

正常模式

  1. 竞争激烈时性能优先:
    • 在正常模式下,锁的获取是非公平的,新到达的 goroutine 可能会直接获取锁,而不需要等待。
  2. 自旋等待:
    • 当锁被占用时,新到达的 goroutine 会自旋等待一段时间,尝试直接获取锁,而不是立即进入休眠状态。
  3. 适用场景:
    • 适用于竞争不激烈或锁持有时间较短的场景,可以最大化性能。

饥饿模式

  • 公平性优先:
    • 在饥饿模式下,锁的获取是公平的,等待时间最长的 goroutine 会优先获取锁。
  • 防止长时间等待:
    • 当某个 goroutine 等待锁的时间超过一定阈值(默认为 1 毫秒),锁会进入饥饿模式。
  • 适用场景:
    • 适用于竞争激烈或锁持有时间较长的场景,可以防止某些 goroutine 长时间等
作者

ShiHaonan

发布于

2025-03-03

更新于

2025-04-27

许可协议

评论