一、sync.Mutex
1.1 锁升级
-
乐观锁: 即自旋+cas
-
悲观锁:阻塞当前goroutine等待被唤醒。
1.2 Mutex模式
type Mutex struct {
// 锁状态, int32 的bitmap, 最后一个bit位表示是否上锁,还有一些其他的标志位标识其他信息。
state int32
// 信号量
sema uint32
}
1.2.1 Normal 正常模式
Normal为mutex互斥锁的默认模式,当一个goroutine尝试获取锁时,如果锁处于加锁状态,该goroutine将尝试以自旋的方式抢锁,当锁被释放时,该goroutine将与阻塞队列被唤醒的头部goroutine进行抢锁,若成功抢锁,被唤醒的goroutine会被重新放回阻塞队列头部,等待下次唤醒;若经过一定次数的自旋后仍不得锁,将会被加入阻塞队列。
当阻塞队列存在goroutine抢锁达到1ms仍抢不到锁时,会进入饥饿模式。
1.2.2 Straving 饥饿模式
饥饿模式下,新到来的goroutine不会进行自旋抢锁,而是被直接放入阻塞队列末尾,等待被唤醒。
当阻塞队列为空,或阻塞队列抢锁的goroutine等待时间小于1ms时,回到正常模式。
1.3 Lock() 加锁
const (
mutexLocked = 1 << iota // 1
mutexWoken = 2 // 10
mutexStarving = 4 // 100
mutexWaiterShift = iota = 3 // 11
)
func (m *Mutex) Lock() {
// CAS
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
starving := false
awoke := false
iter := 0
old := m.state
// 循环尝试加锁
for {
// 当前已上锁 且 处于正常模式, canSpin判断是否可以自旋
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// Active spinning makes sense.
// Try to set mutexWoken flag to inform Unlock
// to not wake other blocked goroutines.
// old & mutexWoken == 0 说明 无g在抢锁
// old>>mutexWaiterShift != 0 即阻塞队列不为空
// 有阻塞队列,自己又不是被唤醒的,此时尝试吧mutexWoken置为1 标识已有g在抢锁
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
awoke = true
}
// 执行自旋
runtime_doSpin()
// 自旋次数+1
iter++
// m.state 可能其他g改变之后,由加锁状态->解锁, 那么就无法进入当前if分支,即开始尝试加锁
old = m.state
continue
}
// 自旋完,接下来要么加锁成功,要么没有成功需要阻塞挂起
// new期望的新值, old变为new 在后续可能成功也可能失败
new := old
// Don't try to acquire starving mutex, new arriving goroutines must queue.
// 非饥饿模式,尝试加锁, 否则饥饿模式其实放弃了这一轮加锁
if old&mutexStarving == 0 {
// 尝试加锁,随后可能成功也可能失败。
new |= mutexLocked
}
// 老的锁要么是加锁, 要么处于饥饿状态, 此时必定抢锁失败,需要阻塞挂起
if old&(mutexLocked|mutexStarving) != 0 {
// mutexWaiterShift =3 需要将阻塞队列数量递增,加上即可
new += 1 << mutexWaiterShift
}
// The current goroutine switches mutex to starvation mode.
// But if the mutex is currently unlocked, don't do the switch.
// Unlock expects that starving mutex has waiters, which will not
// be true in this case.
// starving 是否饥饿模式可能再后续流程中被修改
// 如果自旋过程中starving变成饥饿模式了, 即需要将new改为饥饿状态
if starving && old&mutexLocked != 0 {
new |= mutexStarving
}
// 如果是被唤醒的g,那么要么抢锁成功,要么抢锁失败回到阻塞队列
// 无论那种情况都需要将mutexWoken 置为0(new &^= mutexWoken) 标识无g在抢锁
if awoke {
// The goroutine has been woken from sleep,
// so we need to reset the flag in either case.
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
new &^= mutexWoken // &^ 这个操作其实吧mutexWoken 变为0
}
// CAS 尝试old 改 new
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)
// 从队列中被唤醒,判断等待时长来确认是否需要变为饥饿模式
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
old = m.state
// 如果是饥饿模式,自己又是从队列中被唤起的,此时肯定可以获取到锁
if old&mutexStarving != 0 {
// If this goroutine was woken and mutex is in starvation mode,
// ownership was handed off to us but mutex is in somewhat
// inconsistent state: mutexLocked is not set and we are still
// accounted as waiter. Fix that.
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
throw("sync: inconsistent mutex state")
}
delta := int32(mutexLocked - 1<<mutexWaiterShift)
if !starving || old>>mutexWaiterShift == 1 {
// Exit starvation mode.
// Critical to do it here and consider wait time.
// Starvation mode is so inefficient, that two goroutines
// can go lock-step infinitely once they switch mutex
// to starvation mode.
//
delta -= mutexStarving
}
atomic.AddInt32(&m.state, delta)
// 加锁跳出
break
}
awoke = true
iter = 0
} else {
// 说明其他g改了state, 这时old变state
old = m.state
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
- runtime_canSpin 判断是否可以自旋?
func sync_runtime_canSpin(i int) bool {
if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 {
return false
}
if p := getg().m.p.ptr(); !runqempty(p) {
return false
}
return true
}
- runtime_doSpin 自旋,底层执行汇编PAUSE,持续消耗cpu时间
func sync_runtime_doSpin() {
procyield(active_spin_cnt)
}
1.4 UnLock() 解锁
func (m *Mutex) Unlock() {
if race.Enabled {
_ = m.state
race.Release(unsafe.Pointer(m))
}
// Fast path: drop lock bit.
new := atomic.AddInt32(&m.state, -mutexLocked)
// 解锁成功直接返回,没有进入unlockSlow(new)
if new != 0 {
// Outlined slow path to allow inlining the fast path.
// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
// 解锁后看看是否需要唤醒阻塞队列
m.unlockSlow(new)
}
}
func (m *Mutex) unlockSlow(new int32) {
// 先看是不是本身没有被锁,是则panic
if (new+mutexLocked)&mutexLocked == 0 {
fatal("sync: unlock of unlocked mutex")
}
// 正常模式
if new&mutexStarving == 0 {
old := new
for {
// If there are no waiters or a goroutine has already
// been woken or grabbed the lock, no need to wake anyone.
// In starvation mode ownership is directly handed off from unlocking
// goroutine to the next waiter. We are not part of this chain,
// since we did not observe mutexStarving when we unlocked the mutex above.
// So get off the way.
// 阻塞队列是否为空,为空直接返回,
// 如果mutexLocked、mutexWoken、mutexStarving有一个不为0
// mutexlocked 为1表示又被加锁了,不用处理后续,
// mutexWoken 为1表示又g在抢锁,不用后续处理
// mutexStarving 如果饥饿模式,不用后续处理
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// 阻塞队列数量-1,并记录为有g在抢锁,即自己在抢
new = (old - 1<<mutexWaiterShift) | mutexWoken
// cas 尝试修改
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// 修改成功 唤醒队列头部即可
runtime_Semrelease(&m.sema, false, 1)
return
}
// 其他g改了,则开启下一轮循环
old = m.state
}
} else {
// Starving mode: handoff mutex ownership to the next waiter, and yield
// our time slice so that the next waiter can start to run immediately.
// Note: mutexLocked is not set, the waiter will set it after wakeup.
// But mutex is still considered locked if mutexStarving is set,
// so new coming goroutines won't acquire it.
// 饥饿模式,唤醒下个阻塞队列里的g即可
runtime_Semrelease(&m.sema, true, 1)
}
}
二、sync.RWMutex
读写锁适用于读多写少的场景
1.1 RWMutex
type RWMutex struct {
w Mutex // held if there are pending writers
// 写阻塞队列的信号量
writerSem uint32 // semaphore for writers to wait for completing readers
// 读阻塞队列的信号量
readerSem uint32 // semaphore for readers to wait for completing writers
// 等待或占用读锁的g的数量
readerCount int32 // number of pending readers
// 占用读锁的g的数量
readerWait int32 // number of departing readers
}
1.2 RLock 读加锁
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 正常情况下readerCount都是+1都是大于0, 只有在之前有写锁出现时,readerCount 会被减 (1<<30)
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// A writer is pending, wait for it.
// 读挂起
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
1.3 RUnlock 读解锁
// on entry to RUnlock.
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
// readerCount-1 < 0 说明之前有写锁
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
// 校验
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
fatal("sync: RUnlock of unlocked RWMutex")
}
// readerWait -1 == 0 表示 说明当前是最后一个释放读锁的,需要唤醒写阻塞队列
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// The last reader unblocks the writer.
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
1.4 Lock 写锁
const rwmutexMaxReaders = 1 << 30
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// First, resolve competition with other writers.
// 加互斥锁, 即使加锁成功,也要看之前是否有读锁
rw.w.Lock()
// Announce to readers there is a pending writer.
// 原子操作- 再+, 这个时候r 如果不为0 即肯定有读锁
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
// 需要等待的读的g的数量加到readerWait里
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
// 阻塞挂起
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
1.5 Lock 解写锁
func (rw *RWMutex) Unlock() {
if race.Enabled {
_ = rw.w.state
race.Release(unsafe.Pointer(&rw.readerSem))
race.Disable()
}
// Announce to readers there is no active writer.
// readerConnt + rwmutexMaxReaders >= rwmutexMaxReaders 要么没有加过写锁,要么读g超过数量
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {
race.Enable()
fatal("sync: Unlock of unlocked RWMutex")
}
// 唤醒所有的读,优先释放读,读会先去竞争读锁
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// 释放互斥锁,再释放写锁,其他写锁的g 再去竞争写锁
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
}