Golang的mutex源码阅读
创始人
2025-06-01 00:51:55
0

提前准备

持有锁的标记 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 int64starving := falseawoke := falseiter := 0old := m.statefor {// 可以自旋,处于上锁且不处于饥饿状态时,即自旋尝试唤醒goroutineif 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.statecontinue}new := old// Don't try to acquire starving mutex, new arriving goroutines must queue.// 如果当前互斥锁处于饥饿状态,就把新的状态设置为锁定if old&mutexStarving == 0 {new |= mutexLocked}// 如果mutex处于上锁状态或者饥饿状态,就把waiter数量+1if old&(mutexLocked|mutexStarving) != 0 {// new的二进制第4位+1,即waiter数量+1 1 << mutexWaiterShift = 00000000000000000000000000001000new += 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}// 如果上一次要唤醒这个goroutineif 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 != 0if 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,饥饿状态下,优先处理等待时间长的goroutineruntime_SemacquireMutex(&m.sema, queueLifo, 1)// 根据等待时间,更新mutex的饥饿标志starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = 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 = trueiter = 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 := newfor {// 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)//00000000000000000000000000000101fmt.Printf("判断mutex被锁住或者处于饥饿状态和该值做与操作,为0即为不处于这两种状态    mutexLocked|mutexStarving = %032b", mutexLocked|mutexStarving)}

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...