大家好,我是 frank。「Golang语言开发栈」公众号作者。
01 、介绍
本文通过阅读 Go 语言 sync.Mutex
的源码,我们一起学习 sync.Mutex
的底层实现。
02 、`sync.Mutex` 源码[1]分析
我们通过阅读 Go 语言 sync.Mutex
的源码,可以发现 sync.Mutex
结构体包含两个字段:
type Mutex struct {
state int32
sema uint32
}
state
:存储互斥锁的状态信息,包括锁是否被占用、是否进入饥饿模式,以及是否有协程被唤醒等。sema
:信号量,用于阻塞和唤醒等待该互斥锁的协程。state 字段
state
是一个 int32
类型的整数,用来表示互斥锁的当前状态。它并不是一个简单的布尔值,而是通过多个位(bit)来记录锁的不同状态。通过位运算,可以在同一个字段中存储多种状态信息。我们通过阅读 lockSlow()
方法的源码,可以发现 state
包含的几种状态。
state
主要包含以下几种状态:
const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexStarving
mutexWaiterShift
starvationThresholdNs = 1e6
)
mutexLocked
是通过位移操作 1 << iota
定义的,当其值为 1 时,表示互斥锁已被锁定。state
字段右移来记录当前有多少协程处于等待状态(每个等待的协程增加一个值)。推荐读者朋友们在项目开发中,多尝试使用位运算。
此外,我们通过阅读 lockSlow()
方法的源码,发现其内部实现中,使用“自旋锁”和“CAS”,分别是 runtime_doSpin()
和 atomic.CompareAndSwapInt32()
。
使用“自旋锁”,当互斥锁可能很快被释放时,协程可能会短暂地自旋等待,从而减少 CPU 上下文切换的开销。
需要注意的是“自旋锁”会占用 CPU 资源,我们在项目开发中使用时,切勿长时间进行自旋等待。
使用“CAS”,用于对 state
变量进行原子更新,确保线程安全。
sema 字段
sema
是一个 uint32
类型的信号量,用来控制阻塞和唤醒等待互斥锁的协程。它通过与操作系统底层机制交互,负责在锁被占用时阻塞协程,当锁被释放时唤醒等待中的协程。
当一个协程尝试获取锁但锁已被占用时,它需要进入阻塞状态。 sema
字段与 Go 的 runtime(运行时)机制合作,将这些等待的协程挂起。当锁被释放时,runtime 会通过信号量来唤醒一个或多个等待的协程。
我们阅读 lockSlow()
方法和 unlockSlow()
方法的源码,可以发现 sema
通过 runtime_SemacquireMutex()
和 runtime_Semrelease()
函数进行操作。runtime_SemacquireMutex()
阻塞当前协程并等待信号量,runtime_Semrelease()
则负责释放信号量并唤醒等待的协程。
通过使用信号量,可以很好地处理高并发下的协程调度问题。与自旋锁不同,信号量机制不会占用 CPU 资源。当协程需要等待锁时,它可以通过信号量进入休眠,等待锁释放后再被唤醒,避免了忙等待带来的性能损耗。
03 、总结
本文我们通过阅读 Go 语言 sync.Mutex
的源码,更加深入了解 sync.Mutex
的底层实现,它包含两种操作模式,分别是:
普通模式:
在普通模式下,等待的协程按 FIFO 顺序排队,但新到达的协程可以和被唤醒的协程竞争锁的所有权,因为新协程已经在 CPU 上运行,有一定的优势(即可以减少 CPU 上下文切换,从而提升性能)。如果一个协程等待超过 1 毫秒,互斥锁会切换到饥饿模式。
饥饿模式:
当协程等待超过 1 毫秒时,互斥锁进入饥饿模式。在饥饿模式下,新到达的协程不再直接尝试获取锁,而是排队等待。释放锁的协程会将锁直接交给队列中的第一个等待协程,从而避免长期等待的协程一直得不到锁的情况。
state
字段通过位操作存储了互斥锁的多种状态,包括是否锁定、是否进入饥饿模式、等待队列长度等,允许通过原子操作对这些状态进行高效的并发管理。
sema
字段是一个信号量,用于阻塞和唤醒等待锁的协程,结合 Go runtime 的机制,实现高效的协程调度和唤醒。
这两个字段共同构成了 sync.Mutex
的核心,保证了在高并发场景下的互斥锁操作既高效又安全。
随着 Go 语言版本迭代,sync.Mutex
的实现经过高度优化,能够在低竞争和高竞争场景中提供高效的锁定机制,同时尽量减少协程“饥饿”的情况。
参考资料
[1]
sync.Mutex
源码: https://github.com/golang/go/blob/master/src/sync/mutex.go