Golang的mutex源码阅读

提前准备

持有锁的标记 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)

}

你可能感兴趣的:(golang)