持有锁的标记 mutexLocked = 00000000000000000000000000000001
唤醒标记 mutexWoken = 00000000000000000000000000000010
饥饿标记 mutexStarving = 00000000000000000000000000000100
等待阻塞的water数量 mutexWaiterShift = 00000000000000000000000000000011
饥饿阈值 starvationThresholdNs = 1000000.000000
判断mutex被锁住或者处于饥饿状态和该值做与操作,为0即为不处于这两种状态 mutexLocked|mutexStarving = 00000000000000000000000000000101
1、runtime_canSpin:比较保守的自旋,golang中自旋锁并不会一直自旋下去,在runtime包中runtime_canSpin方法做了一些限制, 传递过来的iter大等于4或者cpu核数小等于1,最大逻辑处理器大于1,时return false; 至少有个本地的P队列,并且本地的P队列可运行G队列为空时 return false。
//go:linkname sync_runtime_canSpin sync.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 }
2、 runtime_doSpin:会调用procyield函数,该函数也是汇编语言实现。函数内部循环调用PAUSE指令。PAUSE指令什么都不做,但是会消耗CPU时间,在执行PAUSE指令时,CPU不会对它做不必要的优化。
//go:linkname sync_runtime_doSpin sync.runtime_doSpin func sync_runtime_doSpin() { procyield(active_spin_cnt) }
3、runtime_SemacquireMutex:
//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex func sync_runtime_SemacquireMutex(addr *uint32) { semacquire(addr, semaBlockProfile|semaMutexProfile) }
4、runtime_Semrelease
//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex func sync_runtime_SemacquireMutex(addr *uint32) { semacquire(addr, semaBlockProfile|semaMutexProfile) }
mutex的源码涉及较多的位运算,上文介绍了部分mutex源码中用到的东西,下面来具体解析mutex源码
Lock
// 如果锁已经在使用中,则调用的goroutine将阻塞,直到锁被释放为止。 func (m *Mutex) Lock() { // 没有上锁的mutex 直接上锁返回,由于速度较快,所以称为fast path // Fast path: grab unlocked mutex. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { // 检测数据竞争 if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return } // 如果没有通过上面的fast path获取到锁,就需要进入lockSlow,通过更复杂的方式获取锁了. // Slow path (outlined so that the fast path can be inlined) m.lockSlow() } // mutex的lockSlow,也是精华所在 func (m *Mutex) lockSlow() { var waitStartTime int64 starving := false awoke := false iter := 0 old := m.state for { // 可以自旋,处于上锁且不处于饥饿状态时,即自旋尝试唤醒goroutine 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:表示当前是否有协程被唤醒。 // old&mutexWoken == 0:表示之前的状态中是否有协程已经被唤醒,如果没有则继续唤醒操作。 // old>>mutexWaiterShift != 0:表示当前等待互斥锁的协程数量不为零。 // atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken):使用原子操作CAS(Compare-And-Swap)修改互斥锁的状态,将原来的状态(old)与mutexWoken(唤醒标志)进行或运算,设置唤醒标志,同时返回操作是否成功的布尔值。 // 即通过atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken)尝试唤醒该goroutine, if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) { awoke = true } // 防止cpu优化指令 runtime_doSpin() iter++ old = m.state continue } new := old // Don't try to acquire starving mutex, new arriving goroutines must queue. // 如果当前互斥锁处于饥饿状态,就把新的状态设置为锁定 if old&mutexStarving == 0 { new |= mutexLocked } // 如果mutex处于上锁状态或者饥饿状态,就把waiter数量+1 if old&(mutexLocked|mutexStarving) != 0 { // new的二进制第4位+1,即waiter数量+1 1 << mutexWaiterShift = 00000000000000000000000000001000 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. // 如果上一循环竞争导致锁进入了饥饿状态,并且mutex是lock状态,就把mutex的状态置为饥饿状态 if starving && old&mutexLocked != 0 { // 把mutex的状态设置为饥饿状态 new |= mutexStarving } // 如果上一次要唤醒这个goroutine 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 位上为 1 的位都清零。清除唤醒标记 new &^= mutexWoken } // 更新状态成功 if atomic.CompareAndSwapInt32(&m.state, old, new) { // 如果mutex没有被锁定也不处于饥饿状态,当前goroutine直接获取锁 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已经在等待了,就按照先进先出的方式, queueLifo := waitStartTime != 0 if waitStartTime == 0 { waitStartTime = runtime_nanotime() } //runtime_SemacquireMutex(&m.sema, queueLifo, 1) 是一个系统调用,它会将当前goroutine挂起, //等待Mutex的解锁。具体来说,它会对Mutex的 sema 字段进行操作,将其值减1,表示Mutex已经被锁定。 //如果此时 sema 的值小于0,说明有其他goroutine正在等待Mutex,当前的goroutine需要将自己加入到等待队列中, //并调用 gopark 函数将自己挂起,等待Mutex释放。 // //这个函数的第一个参数是一个 int32 类型的指针,指向Mutex的 sema 字段。第二个参数 queueLifo 是一个布尔值, //用于表示等待队列的先进先出原则。如果 queueLifo 为true,表示等待队列按照先进先出的原则进行唤醒; //否则,等待队列的唤醒顺序是不确定的。第三个参数 1 表示请求Mutex的数量,通常为1。 // //总之,runtime_SemacquireMutex(&m.sema, queueLifo, 1) 是实现Mutex的加锁操作的核心代码, //它会将当前goroutine挂起,等待Mutex的解锁。通过对Mutex的 sema 字段进行操作, //它保证了Mutex的加锁和解锁操作的原子性和线程安全性。 // 挂起当前goroutine,饥饿状态下,优先处理等待时间长的goroutine runtime_SemacquireMutex(&m.sema, queueLifo, 1) // 根据等待时间,更新mutex的饥饿标志 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. // mutex被锁定或者有唤醒标记,或者等待者数量为0,则状态不相符 if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 { throw("sync: inconsistent mutex state") } // 加锁并且将waiter数减1,此处delta是负数,表示减法 // mutexLocked - 1<>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 { old = m.state } } if race.Enabled { race.Acquire(unsafe.Pointer(m)) } } func (m *Mutex) unlockSlow(new int32) { // 如果对未加锁的mutex解锁直接报错 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在等待锁 if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 { return } // Grab the right to wake someone. // 等待者数量-1,添加唤醒标记 new = (old - 1< 自己调试用到的代码:
const ( // 持有锁的标记 mutexLocked = 1 << iota // mutex is locked // 唤醒标记 mutexWoken // 饥饿标记 mutexStarving // 等待阻塞的water数量 mutexWaiterShift = iota // 饥饿阈值 starvationThresholdNs = 1e6 ) func TestMutex(t *testing.T) { fmt.Println(mutexLocked) fmt.Printf("持有锁的标记 mutexLocked = %032b \n", mutexLocked) fmt.Println(mutexWoken) fmt.Printf("唤醒标记 mutexWoken = %032b \n", mutexWoken) fmt.Println(mutexStarving) fmt.Printf("饥饿标记 mutexStarving = %032b \n", mutexStarving) fmt.Println(mutexWaiterShift) fmt.Printf("等待阻塞的water数量 mutexWaiterShift = %032b \n", mutexWaiterShift) fmt.Printf("等待阻塞的water数量 mutexWaiterShift = %d \n", mutexWaiterShift) fmt.Printf("等待阻塞的water数量 1 << mutexWaiterShift = %032b \n", 1 << mutexWaiterShift) fmt.Println(mutexWaiterShift) fmt.Printf("饥饿阈值 starvationThresholdNs = %f \n", starvationThresholdNs) //00000000000000000000000000000101 fmt.Printf("判断mutex被锁住或者处于饥饿状态和该值做与操作,为0即为不处于这两种状态 mutexLocked|mutexStarving = %032b", mutexLocked|mutexStarving) }