千家信息网

如何理解Go运行时中的Mutex

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章主要讲解了"如何理解Go运行时中的Mutex",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何理解Go运行时中的Mutex"吧!sync.Mu
千家信息网最后更新 2026年11月02日如何理解Go运行时中的Mutex

这篇文章主要讲解了"如何理解Go运行时中的Mutex",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何理解Go运行时中的Mutex"吧!

sync.Mutex是一个high level的同步原语,是为广大的Go开发者开发应用程序提供的一种数据结构,现在它的内部实现逻辑比较复杂了,包含spin和饥饿处理等逻辑,它底层使用了运行时的low level的一些函数和atomic的一些方法。

而运行时中的mutex是为运行时内部使用互斥锁而提供的一个同步原语,它提供了spin和等待队列,并没有去解决饥饿状态,而且它的实现和sync.Mutex的实现也是不一样的。它并没有以方法的方式提供Lock/Unlock,而是提供lock/unlock函数实现请求锁和释放锁。

Dan Scales 今年年初的时候又为运行时的锁增加了static locking rank的功能。他为运行时的架构无关的锁( architecture-independent locks)定义了rank,并且又定义了一些运行时的锁的偏序(此锁之前允许持有哪些锁)。这是运行时锁的一个巨大改变,但是很遗憾并没有一篇设计文档详细去描述这个功能的设计,你可以通过提交的comment(#0a820007)和代码中的注释去了解runtime内部锁的代码变化。

本质上来说,这个功能用来检查锁的顺序是不是按照文档设计的顺序执行的,如果有违反设定的顺序,就有可能死锁发生。因为缺乏准确的文档说明,并且这个功能主要是用来检查运行时锁的执行顺序的,所以在本文中我把这一段逻辑抹去不介绍了。实际Go运行时要开始这个检查的话,你需要设置变量GOEXPERIMENT=staticlockranking。

那么接下来我们看看运行时的mutex的数据结构的定义以及lock/unlock的实现。

运行时mutex数据结构

运行时的mutex数据结构很简单,如下所示,定义在runtime2.go中:

type mutex struct {     lockRankStruct     // Futex-based impl treats it as uint32 key,     // while sema-based impl as M* waitm.     // Used to be a union, but unions break precise GC.     key uintptr }

如果不启用lock ranking,其实lockRankStruct就是一个空结构:

type lockRankStruct struct { }

那么对于运行时的mutex,最重要的就是key字段了。这个字段针对不同的架构有不同的含义。

对于dragonfly、freebsd、linux架构,mutex会使用基于Futex的实现, key就是一个uint32的值。 Linux提供的Futex(Fast user-space mutexes)用来构建用户空间的锁和信号量。Go 运行时封装了两个方法,用来sleep和唤醒当前线程:

  • futexsleep(addr uint32, val uint32, ns int64):原子操作`if addr == val { sleep }`。

  • futexwakeup(addr *uint32, cnt uint32):唤醒地址addr上的线程最多cnt次。

对于其他的架构,比如aix、darwin、netbsd、openbsd、plan9、solaris、windows,mutex会使用基于sema的实现,key就是M* waitm。Go 运行时封装了三个方法,用来创建信号量和sleep/wakeup:

  • func semacreate(mp *m):创建信号量

  • func semasleep(ns int64) int32: 请求信号量,请求不到会休眠一段时间

  • func semawakeup(mp *m):唤醒mp

基于这两种实现,分别有不同的lock和unlock方法的实现,主要逻辑都是类似的,所以接下来我们只看基于Futex的lock/unlock。

请求锁lock

如果不使用lock ranking特性,lock的逻辑主要是由lock2实现的。

func lock(l *mutex) {     lockWithRank(l, getLockRank(l)) } func lockWithRank(l *mutex, rank lockRank) {     lock2(l) } func lock2(l *mutex) {     // 得到g对象     gp := getg()     // g绑定的m对象的lock计数加1     if gp.m.locks < 0 {         throw("runtime·lock: lock count")     }     gp.m.locks++     // 如果有幸运光环,原来锁没有被持有,一把就获取到了锁,就快速返回了     v := atomic.Xchg(key32(&l.key), mutex_locked)     if v == mutex_unlocked {         return     }     // 否则原来的可能是MUTEX_LOCKED或者MUTEX_SLEEPING     wait := v     // 单核不进行spin,多核CPU情况下会尝试spin     spin := 0     if ncpu > 1 {         spin = active_spin     }          for {         // 尝试spin,如果锁已经释放,尝试抢锁         for i := 0; i < spin; i++ {             for l.key == mutex_unlocked {                 if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {                     return                 }             }             // PAUSE             procyield(active_spin_cnt)         }         // 再尝试抢锁, rescheduling.         for i := 0; i < passive_spin; i++ {             for l.key == mutex_unlocked {                 if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {                     return                 }             }             osyield()         }         // 再尝试抢锁,并把key设置为mutex_sleeping,如果抢锁成功,返回         v = atomic.Xchg(key32(&l.key), mutex_sleeping)         if v == mutex_unlocked {             return         }                  // 否则sleep等待         wait = mutex_sleeping         futexsleep(key32(&l.key), mutex_sleeping, -1)     } }

unlock

如果不使用lock ranking特性,unlock的逻辑主要是由unlock2实现的。

func unlock(l *mutex) {     unlockWithRank(l) } func unlockWithRank(l *mutex) {     unlock2(l) } func unlock2(l *mutex) {     // 将key的值设置为mutex_unlocked     v := atomic.Xchg(key32(&l.key), mutex_unlocked)     if v == mutex_unlocked {         throw("unlock of unlocked lock")     }     // 如果原来有线程在sleep,唤醒它     if v == mutex_sleeping {         futexwakeup(key32(&l.key), 1)     }     //得到当前的goroutine以及和它关联的m,将锁的计数减1     gp := getg()     gp.m.locks--     if gp.m.locks < 0 {         throw("runtime·unlock: lock count")     }     if gp.m.locks == 0 && gp.preempt { // restore the preemption request in case we've cleared it in newstack         gp.stackguard0 = stackPreempt     } }

总体来说,运行时的mutex逻辑还不太复杂,主要是需要处理不同的架构的实现,它休眠唤醒的对象是m,而sync.Mutex休眠唤醒的对象是g。

感谢各位的阅读,以上就是"如何理解Go运行时中的Mutex"的内容了,经过本文的学习后,相信大家对如何理解Go运行时中的Mutex这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

运行 逻辑 时中 就是 方法 架构 结构 不同 信号 功能 对象 数据 数据结构 顺序 信号量 尝试 文档 线程 休眠 学习 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 南京欣网互联网络科技 上海挚品互联网科技有限公司 华服互联网科技怎么样 互联网科技工作委员会毛军 A股互联网金融科技公司 互联网科技公司税收优惠 互联网核心科技是什么 西安互联网科技 苏州大垚互联网科技 长亮科技互联网金融 上海亿帐通互联网科技 成都仁贤领冠互联网科技有限公司 成都互联网科技公司工作好不好 互联网金融科技展 互联网科技创新的路径 科技赋能视角下互联网保险发展 互联网科技包装股 抖集互联网科技技术咨询 杭州科技互联网公司有哪些 网互联网络科技 广东万易互联网科技有限公司惠州分公司 互联网科技公司经营范围 中资互联网科技 唐信互联网科技 上海电发互联网科技有限公司 佳云科技和互联网广告 厦门千工互联网科技有限公司 中国互联网十大科技股排名 玉溪新华互联网科技好不好 黑科技 体验场景 互联网大会
0