导航菜单

Mutex 实现原理

🔴 困难

题目描述

解释 Go 的 sync.Mutex 的底层实现原理。为什么 Mutex 不能复制?

示例代码

type Counter struct {
    mu    sync.Mutex
    count int
}

// ❌ 错误:Mutex 被复制
func (c Counter) Increment() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

// ✅ 正确:使用指针接收者
func (c *Counter) Increment() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

提示

  • Mutex 包含状态字段
  • 复制后状态不一致
  • 可能导致死锁或数据竞争

解法

参考答案 (3 个标签)
Mutex 底层实现

底层结构

type Mutex struct {
    state int32  // 状态位
    sema  uint32 // 信号量
}

状态位解析

state 的 32 位:
┌────────────────────────────────────────┐
│          31-3位          │ 2 │ 1 │ 0 │
│    waiter(等待者数量)   │W  │L  │L  │
│                         │   │o  │o  │
│                         │   │c  │c  │
│                         │   │k  │k  │
│                         │   │e  │e  │
│                         │   │d  │d  │
└────────────────────────────────────────┘

- locked (bit 0): 是否锁定
- woken (bit 1): 是否有唤醒的 goroutine
- waiter (bit 2-31): 等待的 goroutine 数量

锁的两种模式

正常模式(Normal Mode)

func (m *Mutex) Lock() {
    // 快速路径:无竞争
    if atomic.CompareAndSwapInt32(&m.state, 0, locked) {
        return
    }
    
    // 慢速路径:有竞争
    m.lockSlow()
}

func (m *Mutex) lockSlow() {
    for {
        // 1. 尝试自旋(多核 CPU)
        for i := 0; i < spinMax; i++ {
            if atomic.CompareAndSwapInt32(&m.state, 0, locked) {
                return
            }
            procyield(30)  // 让出 CPU
        }
        
        // 2. 设置 waiter 标志
        old := atomic.AddInt32(&m.state, locked|waiterShift)
        
        // 3. 休眠
        runtime_SemacquireMutex(&m.sema)
        
        // 4. 被唤醒后,与其他 goroutine 竞争
        if atomic.CompareAndSwapInt32(&m.state, old, locked) {
            return
        }
    }
}

特点

  • 新来的 goroutine 与被唤醒的 goroutine 竞争
  • 新来的更容易成功(还在 CPU 上运行)
  • 可能导致”饥饿”,但吞吐量高

饥饿模式(Starvation Mode)

func (m *Mutex) lockSlow() {
    starvation := false
    for {
        // 检查是否进入饥饿模式
        // 等待时间 > 1ms
        if runtime_nanotime()-m.waitStartTime > starvationThresholdNs {
            atomic.AddInt32(&m.state, starvationFlag)
            starvation = true
        }
        
        // 饥饿模式:直接交给队头
        if starvation && (old&locked == 0) {
            atomic.AddInt32(&m.state, -waiterShift)
            return
        }
        
        runtime_SemacquireMutex(&m.sema)
    }
}

触发条件

  • 等待时间超过 1ms
  • goroutine 饥饿

退出条件

  • 等待者只剩 1 个

为什么不能复制

type Counter struct {
    mu    sync.Mutex
    count int
}

// 值接收者:复制了 Mutex
func (c Counter) Increment() {
    c.mu.Lock()    // 复制后的 Mutex,state = 0
    c.count++
    c.mu.Unlock()  // 释放的是复制的 Mutex
}

// 问题:
// 1. 复制后 state 被重置
// 2. 原始 Mutex 可能被锁定
// 3. 状态不一致,导致死锁

示例

func main() {
    counter := Counter{count: 0}
    
    // goroutine 1
    go func() {
        counter.mu.Lock()
        time.Sleep(time.Second)
        counter.mu.Unlock()
    }()
    
    time.Sleep(100 * time.Millisecond)
    
    // goroutine 2:复制了 Mutex
    counter.Increment()  // ❌ 可能死锁
}

noCopy 机制

type Mutex struct {
    state int32
    sema  uint32
    // noCopy 字段:防止复制
    noCopy noCopy
}

type noCopy struct{}

// go vet 会检测到复制
func (*noCopy) Locker() {}

扩展:RWMutex

底层结构

type RWMutex struct {
    w           Mutex        // 写锁
    writerSem   uint32       // 写者等待信号量
    readerSem   uint32       // 读者等待信号量
    readerCount int32        // 读者数量
    readerWait  int32        // 写者等待时,读者数量
}

读写锁规则

func (rw *RWMutex) RLock() {
    // readerCount < 0:有写者等待
    if atomic.AddInt32(&rw.readerCount, 1) < 0 {
        // 等待写者完成
        runtime_SemacquireMutex(&rw.readerSem)
    }
}

func (rw *RWMutex) Lock() {
    // 获取写锁
    rw.w.Lock()
    
    // 阻止新读者
    r := atomic.AddInt32(&rw.readerCount, -rwmaxReaders) + rwmaxReaders
    if r != 0 {
        // 等待现有读者完成
        atomic.AddInt32(&rw.readerWait, r)
        runtime_SemacquireMutex(&rw.writerSem)
    }
}

RWMutex 问题

// ❌ 不能嵌套
func nested() {
    rw.RLock()
    rw.RLock()  // 可重入
    rw.Lock()   // ❌ 死锁!
    rw.Unlock()
    rw.RUnlock()
    rw.RUnlock()
}

// ❌ 读锁升级为写锁会死锁
func upgrade() {
    rw.RLock()
    // ...
    rw.Lock()   // ❌ 死锁!
    rw.Unlock()
    rw.RUnlock()
}

搜索