马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
互斥锁的定义
- type Mutex struct {
- state int32
- sema uint32
- }
复制代码一个 sema,背后实际上 是一个 休眠队列,可以看下上篇。
一个state,这个状态 分为4个部分。
后三位 各自代表一个状态。 前29位代表最大可等待协程的个数。
state的结构- locked 是否加锁 1加锁,0 正常 占1位
- woken 是否醒来 占1位
- starving 是否饥饿模式 占1位
- waiterShift 等待的数量 占29位
复制代码 底层的定义,下面看代码时候,会说明。
正常模式
加锁
假设现在来了2个g,都想加锁,但是只有一个能成功,2个都通过 atomic.CompareAndSwapInt32(lock, 0 ,1) 伪代码去更改 locked 位置。
 改成功的g获取了锁,没成功的g先自旋几次,然后如果还是未获取到锁,则进入sema休眠队列。
 未成功的g进入休眠队列,把waiterShift加1。
通过这个结论,看代码验证下:- mutexLocked = 1 << iota // mutex is locked
- mutexWoken
- mutexStarving
- mutexWaiterShift = iota
- func (m *Mutex) Lock() {
- // 先给state的最后一位 写 1
- if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
- if race.Enabled {
- race.Acquire(unsafe.Pointer(m))
- }
- 写上了, 加锁成功,直接返回。
- return
- }
- // 写不上进入这个方法
- m.lockSlow()
- }
- // 不是完整代码,只截取和这里相关的部分
- func (m *Mutex) lockSlow() {
- starving := false
- iter := 0
- old := m.state
- for {
- // 是否是饥饿模式 是否还能自旋 iter会记录自旋次数
- if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
- runtime_doSpin()
- iter++ // 自旋次数加1
- old = m.state
- continue
- }
- // 自旋一定次数后
- new := old
- // 判断是否是饥饿模式
- if old&mutexStarving == 0 {
- new |= mutexLocked
- }
- if atomic.CompareAndSwapInt32(&m.state, old, new) {
- runtime_SemacquireMutex(&m.sema, queueLifo, 1)
- // 进入了休眠 ,不会执行下面的语句了。直到被唤醒
- starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
- }
- }
复制代码 小结:- 尝试CAS直接加锁
- 若无法直接获取,进行多次自旋尝试
- 多次尝试失败,进入sema队列休眠
复制代码 如果这个时候,再来一个:
也是同样,进入sema的休眠队列。
解锁
解锁的这个g,除了修改locked的值,还需要去判断waiterShift,有没有协程在等,如果有,要去唤醒一个协程。
看代码:
[code]func (m *Mutex) Unlock() { // 减去1,发现state的值,不是0,说明有协程在等 new := atomic.AddInt32(&m.state, -mutexLocked) if new != 0 { m.unlockSlow(new) }}func (m *Mutex) unlockSlow(new int32) { if new&mutexStarving == 0 { // 这里是讲了 非饥饿模式 old := new for { new = (old - 1 |