作者:军校招生顾问天使 | 来源:互联网 | 2023-09-24 19:19
文章目录定义介绍模式转换简单模式正常模式饥饿模式阻塞与解锁加锁自旋定义介绍先从sync.Mutex定义出发,看看mutex是如何实现;sync.Mutex源码se
定义介绍
先从sync.Mutex定义出发,看看mutex是如何实现; sync.Mutex源码
- sema: 信号量, 用于阻塞和唤醒goroutine; 注意, sema并不存储阻塞的goroutine的数量;
- state: 状态(精彩之处); 它的使用被切分2部分,一部分是状态标识位,另一部分存储wait goroutine的数量;
源代码常量定义 | 含义 | 值 |
---|
mutexLocked | 锁定标志位 | 001 |
mutexWoken | 唤醒标志位 | 010 |
mutexStarving | 饥饿标志位 | 100 |
mutexWaiterShift | 等待计数偏移 | 3 |
所以,锁竞争可以理解为 当前goroutine是否将锁定标志位 设置成1; 正常模式是多goroutine抢占锁定标志位的过程;饥饿模式是饥饿标志位被设置成1;
模式转换
结合代码分析以上3种方式,实际是2种,即正常模式和饥饿模式;我这里将正常模式细分出简单模式和正常模式; 首先回忆一下锁的用法, 即
func xxxxx() {mu.Lock()defer mu.Unlock()......
}
当多个goroutine 抢锁时,成功的可以继续执行,失败的则被阻塞;等同于 抢锁成功的可以从Lock函数中返回,失败则留在Lock函数中无法返回。
简单模式
func (m *Mutex) Lock() {if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {...return}m.lockSlow()
}func (m *Mutex) Unlock() {...new := atomic.AddInt32(&m.state, -mutexLocked)if new != 0 {m.unlockSlow(new)}
}
代码容易理解 当G抢锁时state=0变成state=1,加锁成功; 当G解锁时state=1变成state=0 解锁成功;
正常模式
func (m *Mutex) unlockSlow(new int32) {...if new&mutexStarving &#61;&#61; 0 {old :&#61; newfor {if old>>mutexWaiterShift &#61;&#61; 0 || old&(mutexLocked|mutexWoken|mutexStarving) !&#61; 0 {return}new &#61; (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {runtime_Semrelease(&m.sema, false, 1)return}old &#61; m.state}} else {...}
}
先看正常模式的解锁操作&#xff0c;已将饥饿模式代码隐藏; new &#61; m.state - mutexLocked, 意味着 锁定标志被解除; 正常模式解锁 会将state 唤醒标志置1, 并唤醒G
old :&#61; newfor {if old>>mutexWaiterShift &#61;&#61; 0 || old&(mutexLocked|mutexWoken|mutexStarving) !&#61; 0 {return}
runtime_Semrelease(&m.sema, false, 1) 队尾唤醒G
runtime_Semrelease(&m.sema, true, 1) 队首唤醒G
然后看一下正常模式的加锁操作&#xff1b;已将饥饿模式代码隐藏; 前文所过加锁成功可以等同于Lock函数返回&#xff0c;所以紧盯break; 接下来将分析如何退出;
func (m *Mutex) lockSlow() {var waitStartTime int64 starving :&#61; false awoke :&#61; false iter :&#61; 0 old :&#61; m.state for {......new :&#61; oldif old&mutexStarving &#61;&#61; 0 {new |&#61; mutexLocked}if old&(mutexLocked|mutexStarving) !&#61; 0 {new &#43;&#61; 1 << mutexWaiterShift}if starving && old&mutexLocked !&#61; 0 {new |&#61; mutexStarving}if awoke {if new&mutexWoken &#61;&#61; 0 {throw("sync: inconsistent mutex state")}new &^&#61; mutexWoken}if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) &#61;&#61; 0 { break }queueLifo :&#61; waitStartTime !&#61; 0if waitStartTime &#61;&#61; 0 {waitStartTime &#61; runtime_nanotime()}runtime_SemacquireMutex(&m.sema, queueLifo, 1)starving &#61; starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold &#61; m.stateif old&mutexStarving !&#61; 0 {...}awoke &#61; trueiter &#61; 0} else {old &#61; m.state}}...
}
饥饿模式
当唤醒G等待时间大于1ms时&#xff0c;即将进入模式&#xff0c;那么后续锁竞争的G 将直接进入等待队列&#xff0c;无需竞争;
func (m *Mutex) lockSlow() {var waitStartTime int64 starving :&#61; false awoke :&#61; false iter :&#61; 0 old :&#61; m.state for {...new :&#61; old...if old&(mutexLocked|mutexStarving) !&#61; 0 {new &#43;&#61; 1 << mutexWaiterShift}if starving && old&mutexLocked !&#61; 0 {new |&#61; mutexStarving}...if atomic.CompareAndSwapInt32(&m.state, old, new) {....queueLifo :&#61; waitStartTime !&#61; 0if waitStartTime &#61;&#61; 0 {waitStartTime &#61; runtime_nanotime()}runtime_SemacquireMutex(&m.sema, queueLifo, 1)starving &#61; starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold &#61; m.stateif old&mutexStarving !&#61; 0 {if old&(mutexLocked|mutexWoken) !&#61; 0 || old>>mutexWaiterShift &#61;&#61; 0 {throw("sync: inconsistent mutex state")}delta :&#61; int32(mutexLocked - 1<<mutexWaiterShift)if !starving || old>>mutexWaiterShift &#61;&#61; 1 {delta -&#61; mutexStarving}atomic.AddInt32(&m.state, delta)break}...} else {old &#61; m.state}}...
}
阻塞与解锁
加锁自旋
自旋就是空跑阻止G进入阻塞队列; 因为G的唤醒和阻塞涉及G的调度, 调度是耗时的; 如果通过短暂的自选可以获取锁&#xff0c;那就避免G的调度耗时; 同时可以看到自旋会浪费CPU的;
func (m *Mutex) lockSlow() {...awoke :&#61; falseiter :&#61; 0old :&#61; m.statefor {if old&(mutexLocked|mutexStarving) &#61;&#61; mutexLocked && runtime_canSpin(iter) {if !awoke && old&mutexWoken &#61;&#61; 0 && old>>mutexWaiterShift !&#61; 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke &#61; true}runtime_doSpin()iter&#43;&#43;old &#61; m.statecontinue}new :&#61; old...}
runtime_canSpin&#xff0c;进入自旋的条件是严苛的&#xff0c;尽力避免浪费CPU;
如有不对之处&#xff0c;欢迎留言评论区