golang mutex源码理解

尝试用自己的理解来解读sync.Mutex源码,

mutex锁的 state值第一位0表示未上锁 1表示已上锁
第二位表示唤醒标识位,如果为1表示有goroutine在唤醒状态,释放锁的goroutine如果看到这个位为1则不用特意去唤醒其他goroutine
第三位表示是否进入饥饿模式

func (m *Mutex) Lock() {
	// Fast path: grab unlocked mutex.
	//快速加锁,只有在m.state的值为0时,才能成功,这意味着没有任何其他goroutine跟自己抢锁或在队列中等待
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}
	// Slow path (outlined so that the fast path can be inlined)
	//如果快速加锁失败,则进入慢速方法
	m.lockSlow()
}

m.lockSlow()

func (m *Mutex) lockSlow() {
	//当前goroutine等待锁时间
	var waitStartTime int64
	//当前goroutine 是否有把锁转换为饥饿模式的意向
	starving := false
	//当前goroutine是否获取唤醒位
	awoke := false
	//这个是用在自旋检查的吧(?)
	iter := 0
	//获取锁的状态信息
	old := m.state
	for {
		// Don't spin in starvation mode, ownership is handed off to waiters
		// so we won't be able to acquire the mutex anyway.
		// 如果在上锁状态,并且没有进入到饥饿模式,且自旋检查通过,则不断自旋,最大次数为4
		// 自旋主要是看锁的上锁位是否为0,一但发现为零则跳出自旋去抢
		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.
			//抢占唤醒标识位 如果awoke为false(暂未持有唤醒位) old&mutexWoken==0 锁的唤醒位也没被占 old>>mutexWaiterShift != 0 (队列也不为空)
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				awoke = true
			}
			//自旋
			runtime_doSpin()
			iter++
			old = m.state
			continue
		}
		new := old
		// Don't try to acquire starving mutex, new arriving goroutines must queue.
		//如果锁在普通模式 则new设为尝试获取锁
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}
		//如果锁没有被释放或者锁处于饥饿模式下,则队列+1 表示 当前goroutine准备去排队
		if old&(mutexLocked|mutexStarving) != 0 {
			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.
		//如果当前goroutine有将锁转换成饥饿模式的意向并且锁在上锁状态 则new饥饿模式位设为1
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}
		//如果持有唤醒标识,则new中要释放掉
		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
		}
		//进行原子操作m.state
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			//原子操作成功,下面的判断可得知 原子操作时锁不在上锁状态且不在饥饿模式,那么就是当前goroutine获取到了锁 break跳出循环获取到锁
			if old&(mutexLocked|mutexStarving) == 0 {
				break // locked the mutex with CAS
			}
			// If we were already waiting before, queue at the front of the queue.
			//如果当前goroutine等待时间不为0,则表示是第一次进来,没有排过队,则要进入队尾,否则则是排过队的goroutine,则去队头 queueLifo 表示是否后进先出
			queueLifo := waitStartTime != 0
			//设置等待时间表示下次代码跑到这个地方可以去队头
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
			//goroutine陷入队列中睡眠,等待唤醒
			runtime_SemacquireMutex(&m.sema, queueLifo, 1)
			//当goroutine再次被唤醒 如果检查排队时间超过了starvationThresholdNs 则要将锁设为饥饿模式 由于starving 的值只在这个地方可以被 改为true,所以只要排队时间过长后就一直会starving == true
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			//获取最新锁状态
			old = m.state
			//如果锁现在是饥饿模式,则认为当前goroutine是因为其它goroutine释放锁后唤醒的
			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")
				}
				//队列长度减1
				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
			}
			//上面的if 不成立,表示当前锁为普通模式,则从头再循环并且
			awoke = true //接下来跳过唤醒标识抢占
			iter = 0
		} else {
			old = m.state
		}
	}

}

释放锁

func (m *Mutex) Unlock() {
	if race.Enabled {
		
	}

	// Fast path: drop lock bit.
	//对锁减1
	new := atomic.AddInt32(&m.state, -mutexLocked)
	//如果锁减1后变为0,则表示没有其它goroutine等待锁,可以直接返回,否则进入到慢方法处理
	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) {
//将快速方法中减了1的new加回1看看是否还上锁,如果锁位是0则有问题
	if (new+mutexLocked)&mutexLocked == 0 {
		throw("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.
			//如果等待队列为0则没有goroutine要唤醒的直接返回,或上锁位为1 或有goroutine被唤醒或锁进入了饥饿模式都不用再做其他什么事了
			if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
				return
			}
			// Grab the right to wake someone.
			//队列数量减1 并且唤醒标识位置1
			new = (old - 1<<mutexWaiterShift) | mutexWoken
			//尝试唤醒一个goroutine 
			//代码如果能执行到这里,说明 等待队列里有待唤醒的goroutine,并且 锁已经被当前goroutine释放了,不处于饥饿模式,没有已经被唤醒的goroutine 下面的cas操作失败只能是因为 有新的goroutine通过自旋抢到了锁或抢到了唤醒标识,这样也不用去唤醒队列中的其它goroutine
			if atomic.CompareAndSwapInt32(&m.state, old, new) {
				runtime_Semrelease(&m.sema, false, 1)
				return
			}
			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.
		//如果是在饥饿模式下,那一定要去唤醒一个goroutine 注意这里是没有将队列减1的,而是交给唤醒的goroutine去做
		runtime_Semrelease(&m.sema, true, 1)
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值