目录
Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。
Mutex在第一次使用后不能被复制.
二、sync.Locker 接口
type Locker interface{
Lock()
Unlock()
}
三、sync.Mutex 常量
const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexStarving
mutexWaiterShift = iota
starvationThresholdNs = 1e6
)
mutexLocked:锁定状态,值为1,二进制:0001
mutexWoken:唤醒状态,值为2,二进制:0010mutexStarving:饥饿状态,值为4,二进制:0100
mutexWaiterShift:等待goroutine偏移量,值为3
starvationThresholdNs:进入饥饿模式,等待纳秒数,等于1ms
四、 Mutex操作模式:正常和饥饿
在正常模式下,等待者按照FIFO顺序排队,但是唤醒的等待goroutine并不拥有互斥锁,而是与新到达的goroutines争夺互斥锁的所有权。新加入的goroutines有一个优势——它们已经在CPU上运行,在获取不到锁的情况下,可以进行自旋,尝试获得锁,所以一个唤醒的等待者很有可能会输。在这种情况下,它被排在等待队列的前面。
在饥饿模式下,互斥锁的所有权直接从正在解锁的goroutine传递给队列前面的等待goroutine。新的goroutine不会试图获取互斥锁,即使它看起来已经解锁,也不会试图旋转。相反,他们把自己排在等待队列的尾部。
正常模式与饥饿模式之间的切换:
如果一个等待goroutine获得互斥锁的时间超过1ms,它将互斥锁切换到饥饿模式。
如果一个等待goroutine收到互斥锁的所有权,并且它是队列中的最后一个等待者,或者它等待的时间少于1毫秒,它会将互斥锁切换回正常操作模式。
** 普通模式的性能要好得多,因为即使有阻塞的等待器,goroutine也可以连续多次获取互斥锁。饥饿模式对预防尾潜伏期的病理病例有重要意义。
五、sync.Mutex 结构体
type Mutex struct {
state int32
sema uint32
}
state,长度为32为的整数,通过位操作锁状态、等待者个数:
前29位表示等待goroutine的数量,获取方式:Mutex.state >> mutexWaiterShift
第30位表示是否是饥饿模式,判断依据Mutex.state & mutexStarving,1-饥饿模式,0-正常模式。
第31位表示是否被唤醒,判断依据Mutex.state & mutexWoken,1-正在被唤醒 0-未被唤醒。
第32位表示是否被锁定,判断依据Mutex.state & mutexLocked,1-锁定 0-未锁定。
sema:信号量,用于在获取锁/释放锁时,进入睡眠/被唤醒。
六、Lock方法
func (m *Mutex) Lock() {
// 快速路径:抓取未锁定的互斥锁。
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
// 慢路径
m.lockSlow()
}
func (m *Mutex) lockSlow() {
var waitStartTime int64 //当前goroutine的等待时间
starving := false //当前goroutine是否处于饥饿模式
awoke := false //当前goroutine是否是唤醒状态
iter := 0 //迭代次数
old := m.state //获取锁信息
for {
//在正常模式下,通过runtime_canSpin方法判断是否可以进入cpu自旋
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
//尝试进入唤醒状态
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
awoke = true
}
runtime_doSpin()//自旋
iter++ //更新迭代次数
old = m.state //更新锁信息
continue
}
// 更新state
new := old
// 判断是否是正常模式,若是则更新new的锁定状态
if old&mutexStarving == 0 {
new |= mutexLocked
}
// 当前锁的状态是已加锁且是饥饿模式,当前goroutine进行排队,并更新等待者数量
if old&(mutexLocked|mutexStarving) != 0 {
new += 1 << mutexWaiterShift
}
// 当前goroutine处于饥饿模式,且锁是加锁状态,更新new为饥饿模式
if starving && old&mutexLocked != 0 {
new |= mutexStarving
}
// 当前goroutine处于唤醒状态
if awoke {
// 当前锁若不是唤醒状态,则panic
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
new &^= mutexWoken
}
//通过CAS更新state
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if old&(mutexLocked|mutexStarving) == 0 {
break // locked the mutex with CAS
}
// If we were already waiting before, queue at the front of the queue.
queueLifo := waitStartTime != 0
if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
}
runtime_SemacquireMutex(&m.sema, queueLifo, 1) // 通过信号阻塞当前goroutine,等待唤醒
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
old = m.state
if old&mutexStarving != 0 { //饥饿模式
// 如果当前state已锁定,已唤醒或等待者为0,则panic
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
throw("sync: inconsistent mutex state")
}
delta := int32(mutexLocked - 1<<mutexWaiterShift)// 等待者goroutine数量减一
if !starving || old>>mutexWaiterShift == 1 { //若当前goroutine非饥饿模式或等待者数量为1,则切换正常模式
delta -= mutexStarving
}
atomic.AddInt32(&m.state, delta)//更新state
break
}
awoke = true
iter = 0
} else {
old = m.state
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
七、Unlock
//互斥锁解锁
func (m *Mutex) Unlock() {
if race.Enabled {
_ = m.state
race.Release(unsafe.Pointer(m))
}
// 快速解锁
new := atomic.AddInt32(&m.state, -mutexLocked)
if new != 0 {//存在其它等待goroutine
m.unlockSlow(new)
}
}
func (m *Mutex) unlockSlow(new int32) {
// 判断是否处于未加锁状态,在未加锁状态下进行解锁,会panic
if (new+mutexLocked)&mutexLocked == 0 {
throw("sync: unlock of unlocked mutex")
}
if new&mutexStarving == 0 {//正常模式
old := new
for {
//若是有等待goroutine或已经有加锁、唤醒、饥饿时,则返回
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// 等待goroutine个数减一,且设置唤醒状态
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// 通过信号唤醒一个goroutine,但不是第一个
runtime_Semrelease(&m.sema, false, 1)
return
}
old = m.state
}
} else {
// 饥饿模式下,通过信号唤醒第一个等待goroutine
runtime_Semrelease(&m.sema, true, 1)
}
}
八、Mutex中使用的runtime*
runtime_canSpin:
runtime_canSpin对应runtime/proc.go 的sync_runtime_canSpin方法,用于判断是否可以进行自旋操作
// sync.Mutex 的主动自旋
//go:linkname sync_runtime_canSpin sync.runtime_canSpin
//go:nosplit
func sync_runtime_canSpin(i int) bool {
// 若迭代次数超过active_spin(4),或 cpu核数为1,或 逻辑处理器>1,返回false
if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 {
return false
}
//若逻辑处理器的本地goroutine队里为空,返回false
if p := getg().m.p.ptr(); !runqempty(p) {
return false
}
return true
}
runtime_doSpin:
runtime_doSpin对应runtime/proc.go 的sync_runtime_doSpin方法,用于自旋操作
runtime_SemacquireMutex:
runtime_SemacquireMutex对应 runtime/sema.go 的sync_runtime_SemacquireMutex方法,通过信号阻塞goroutine,在lockSlow中使用.
runtime_Semrelease:
runtime_Semrelease对应 runtime/sema.go 的 sync_runtime_Semrelease 方法,通过信号唤醒阻塞的goroutine.
本文详细解析了go语言中sync.Mutex的实现,包括Locker接口、Mutex常量、锁的操作模式(正常和饥饿)、Mutex结构体、Lock和Unlock方法以及内部涉及的runtime*函数,探讨了在不同模式下锁的行为及其性能考虑。
1117

被折叠的 条评论
为什么被折叠?



