Go 语言 sync.Mutex 与 RWMutex 源码解析

半杯拿铁
半杯拿铁
发布时间:2026-10-15| 2人浏览过
原创

本文档旨在通过剖析源代码,总结 Go 语言标准库中 Mutex 和 RWMutex 的工作原理。内容涵盖互斥锁的快速/慢速处理流程、自旋与信号量切换机制,以及读写锁的读者-_writer_计数协调策略。

Mutex (互斥锁) 内部机制

sync.Mutex 是 Go 中最基础的同步原语。其设计核心在于兼顾无竞争时的低开销(CAS)与高竞争时的公平性(饥饿模式)。

Lock() 快速路径:原子操作上锁

当 Mutex 未被锁定且无等待者时,通过 atomic.CompareAndSwapInt32 将状态从 0 变为 mutexLocked。这是最快的路径,直接返回。

// Lock locks m.
func (m *Mutex) Lock() {
  if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
    return
  }
  m.lockSlow()
}

lockSlow():阻塞、自旋与状态管理

当快速路径失败时,进入 lockSlow 循环。该函数负责处理自旋等待、入队以及饥饿模式的触发。

  1. 自旋等待:如果当前处于正常模式且存在其他 Goroutine 在等待(waiters > 0),且锁已被占用,代码尝试进行短暂自旋(runtime_doSpin),在此期间标记 mutexWoken 以避免后续解锁唤醒多余的 Goroutine。
    if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
      // ...设置woken标志...
      runtime_doSpin()
      iter++
      old = m.state
      continue
    }
  2. CAS 更新状态:计算期望状态 new:
    - 若非饥饿态且未加锁,加入 mutexLocked。
    - 若被锁或饥饿,增加 Waiter 数量(左移 mutexWaiterShift)。
    - 若此前已自旋唤醒过 (awoke),清除该标志。
  3. 阻塞入队:若 CAS 修改成功但此时锁仍被占用,说明当前 Goroutine 需要等待。调用 runtime_SemacquireMutex 进行阻塞。此过程支持 LIFO(后进先出)排队以提升局部性,但在饥饿模式下强制 FIFO。
  4. 饥饿模式判定:Goroutine 被唤醒后,若等待时间超过阈值(默认 1ms)或原本就是饥饿模式,则进入/维持饥饿状态。在此模式下,锁所有权由解锁者直接“交接”给队头等待者,而非随机唤醒,确保先来后到的公平性。

Unlock() 释放锁与交接

释放锁分为两步:

  1. 快速路径:使用 atomic.AddInt32 减去 mutexLocked。如果返回值不为 0,说明有等待者或有状态残留,进入慢速路径。
  2. UnlockSlow() 交接:
    - 普通模式:若没有 waiter 或已有 Goroutine 被唤醒,直接退出。否则,将 mutexWoken 置位并减少 waiter 数,通过信号量唤醒下一个等待者。
    - 饥饿模式:直接调用 runtime_Semrelease(..., handoff=true)。参数 handoff=true 意味着直接将控制权移交给下一个 waiter,令其立即执行,不再进入 P 的运行队列等待调度,从而大幅降低饥饿状态的延迟。

RWMutex (读写锁) 实现细节

RWMutex 允许多个读协程同时运行,但写协程独占资源。其状态管理依赖于整数的不同比特位。

数据结构核心字段

type RWMutex struct {
    w           Mutex  // 辅助写锁,用于串行化写的请求
    writerSem   uint32 // 写者的信号量,等待活跃读者完成
    readerSem   uint32 // 读者的信号量,等待写者完成
    readerCount int32  // 活跃读者数量(负数表示有写者等待)
    readerWait  int32  // 等待写者完成的读者数量
}

关键位运算技巧:
- readerCount 的最高位被用作“写锁占位符”。
- 常量 rwmutexMaxReaders = 1 << 30。
- 当写入 -rwmutexMaxReaders 时,会将最高位置 1(负数),且剩余位全为 0,标志着有一个写者正在获取锁。

写锁获取 (Lock)

  1. 排除竞争:首先获取内部的 w.Lock(),确保同一时刻只有一个写协程在进行 Lock 的逻辑处理。
  2. 通知读者:通过 atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) 将计数器转为负数,并向所有正在执行的读者广播“有写者在排队”。
  3. 等待读者完成:将当前 readerCount 的数量转移至 readerWait。如果还有活跃读者,阻塞在 writerSem 上,直到所有读者执行完毕并发送信号。

写锁释放 (Unlock)

通过加法将 readerCount 恢复为正数,解除对读者的拦截。随后循环唤醒所有等待在 readerSem 上的读协程,最后释放内部的 w.Unlock(),允许其他写者继续尝试。

读锁获取 (RLock)

  1. 原子递增:执行 AddInt32(&rw.readerCount, 1)。
  2. 冲突检测:检查结果是否为负数。如果是负数,说明有写者正在等待或持有锁。此时当前读协程将被挂起在 readerSem 信号量上,直到写者释放锁。

读锁释放 (RUnlock)

原子递减 readerCount。若结果为负数,说明之前有过写者等待。若该协程是最后一个活跃的读者(readerWait 减至 0),则释放 writerSem 信号量,唤醒正在等待的写协程。

以上就是Go语言互斥锁与读写锁内部实现的详细内容,更多关于Go并发编程的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。