iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Golang中sync.Mutex源码分析
  • 523
分享到

Golang中sync.Mutex源码分析

2023-07-05 12:07:26 523人浏览 八月长安
摘要

本篇内容介绍了“golang中sync.Mutex源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Mutex结构type M

本篇内容介绍了“golang中sync.Mutex源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

Mutex结构

type Mutex struct {state int32sema  uint32}
  • state 记录的状态,转换为二进制前29位表示等待锁的Goroutine数量,后三位从左到右分别表示当前g 是否已获得锁、是否被唤醒、是否正饥饿

  • sema 充当临界资源,其地址作为这个锁在全局的唯一标识,所有等待这个锁的goroutine都会在阻塞前把自己的sudog放到这个锁的等待队列上,然后等待被唤醒,sema的值就是可以被唤醒的goroutine的数目,只有0和1。

常量

const (mutexLocked = 1 << iota // mutex is locked  //值1,转二进制后三位为001,表示锁已被抢mutexWoken                                  //值2,转二进制后三位为010,告诉即将释放锁的g现在已有g被唤醒mutexStarving                               //值4,转二进制后三位为100,表示当前处在饥饿状态mutexWaiterShift = iota                     //值3,表示mutex.state右移3位为等待锁的goroutine数量starvationThresholdNs = 1e6                 //表示mutext切换到饥饿状态所需等待时间的阈值,1ms。)

Locker接口

type Locker interface {Lock()Unlock()}

下面重点看这两个方法。

加锁Lock

Golang中sync.Mutex源码分析

Lock()

func (m *Mutex) Lock() {// 第一种情况:快上锁,即此刻无人来抢锁if atomic.CompareAndSwapint32(&m.state, 0, mutexLocked) {if race.Enabled {  //竞争检测相关,不用看race.Acquire(unsafe.Pointer(m))}return}// 第二种情况:慢上锁,即此刻有竞争对手m.lockSlow()}

CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool){},go的CAS操作,底层通过调用cpu指令集提供的CAS指令实现,位置在src/runtime/internal/atomic/atomic_amd64.s/&middot;Cas(SB)。

  • 参数addr:变量地址

  • 参数old:旧值

  • 参数new:新值

  • 原理:如果addr和old相等,则将new赋值给addr,并且返回true,否则返回false

lockSlow()

// 注释里的第一人称“我”只当前gfunc (m *Mutex) lockSlow() {var waitStartTime int64// 等待开始的时间starving := false// 我是否饥饿awoke := false// 我是否被唤醒iter := 0// 我的自旋次数old := m.state// 这个锁此时此刻所有的信息for {// 如果:锁已经被抢了 或着 正处在饥饿状态 或者 允许我自旋 那么进行自旋if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {// 如果:我没有处在唤醒态 并且 当前无g处在唤醒态 并且            // 有等待锁的g 并且CAS尝试将我置为唤醒态成功 则进行自旋            // 之所以将我置为唤醒态是为了明示那些执行完毕正在退出的g不用再去唤醒其它g了,因为只允许存在一个唤醒的g。if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true}runtime_doSpin()// 我自旋一次iter++// 我自旋次数加1old = m.statecontinue}new := old// new只是个中间态,后面的cas操作将会判断是否将这个中间态落实// 如果不是处在饥饿模式就立即抢锁if old&mutexStarving == 0 {new |= mutexLocked}        // 如果锁被抢了 或者 处在饥饿模式,那就去排队if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift// 等待锁的goroutine数量加1}// 如果我现在饥渴难耐 而且 锁也被抢走了,那就立即将锁置为饥饿模式if starving && old&mutexLocked != 0 {new |= mutexStarving}if awoke {if new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}            // 释放我的唤醒态            // 因为后面我要么抢到锁要么被阻塞,都不是处在和唤醒态new &^= mutexWoken}        //此处CAS操作尝试将new这个中间态落实if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {break // 抢锁成功!}// queueLifo我之前有没有排过队queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}            //原语:如果我之前排过队,这次就把我放到等待队列队首,否则把我放到队尾,并将我挂起runtime_SeMacquireMutex(&m.sema, queueLifo, 1)             // 刚被唤醒的我先判断自己是不是饥饿了,如果我等待锁的时间小于starvationThresholdNs(1ms),那就不饿starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = m.stateif old&mutexStarving != 0 {// 我一觉醒来发觉锁正处在饥饿状态,苍天有眼这个锁属于我了,因为饥饿状态绝对没有人跟我抢锁if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}                // delta是一个中间状态,atomic.AddInt32方法将给锁落实这个状态delta := int32(mutexLocked - 1<<mutexWaiterShift)if !starving || old>>mutexWaiterShift == 1 {                    // 如果现在我不饥饿或者等待锁的就我一个,那么就将锁切换到正常状态。                    // 饥饿模式效率很低,而且一旦有两个g把mutex切换为饥饿模式,那就会死锁。delta -= mutexStarving}                // 原语:给锁落实delta的状态。atomic.AddInt32(&m.state, delta)                // 我拿到锁啦break}            // 把我的状态置为唤醒,我将继续去抢锁awoke = true            // 把我的自旋次数置0,我又可以自旋抢锁啦iter = 0} else {            // 继续去抢锁old = m.state}}    // 竞争检测的代码,不管if race.Enabled {race.Acquire(unsafe.Pointer(m))}}

runtime_canSpin(iter) 判断当前g可否自旋,已经自旋过iter次

func sync_runtime_canSpin(i int) bool {// 可自旋的条件:    // 1.多核cpu    // 2.GOMAXPROCS > 1 且 至少有一个其他的p在运行 且 该p的本地runq为空    // 3.iter小于最大自旋次数active_spin = 4if 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}

runtime_doSpin()通过调用procyield(n int32)方法来实现空耗CPU,n乃空耗CPU的次数。

//go:linkname sync_runtime_doSpin sync.runtime_doSpin//go:nosplitfunc sync_runtime_doSpin() {procyield(active_spin_cnt)}

procyield(active_spin_cnt) 的底层通过执行PAUSE指令来空耗30个CPU时钟周期。

TEXT runtime·procyield(SB),NOSPLIT,$0-0MOVLcycles+0(FP), AXagain:PAUSESUBL$1, AXJNZagainRET

runtime_SemacquireMutex(&m.sema, queueLifo, 1) 将当前g放到mutex的等待队列中去

//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutexfunc sync_runtime_SemacquireMutex(addr *uint32, lifo bool, skipframes int) {semacquire1(addr, lifo, semaBlockProfile|semaMutexProfile, skipframes)}

semacquire1(addr *uint32, lifo bool, profile semaProfileFlags, skipframes int) 若lifo为true,则把g放到等待队列队首,若lifo为false,则把g放到队尾

atomic.AddInt32(int32_t *val, int32_t delta) 原语:给t加上t_delta

uint32_tAddUint32 (uint32_t *val, uint32_t delta){  return __atomic_add_fetch (val, delta, __ATOMIC_SEQ_CST);}

解锁Unlock

Golang中sync.Mutex源码分析

Unlock

func (m *Mutex) Unlock() {if race.Enabled {_ = m.staterace.Release(unsafe.Pointer(m))}// 如果没有g在等待锁则立即释放锁new := atomic.AddInt32(&m.state, -mutexLocked)if new != 0 {// 如果还有g在等待锁,则在锁释放后需要做一点收尾工作。m.unlockSlow(new)}}

unlockSlow

func (m *Mutex) unlockSlow(new int32) {if (new+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}    // 如果锁处在正常模式下if new&mutexStarving == 0 {old := newfor {// 如果锁正处在正常模式下,同时 没有等待锁的g 或者 已经有g被唤醒了 或者 锁已经被抢了,就什么也不用做直接返回// 如果锁正处在饥饿模式下,也是什么也不用做直接返回if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}// 给锁的唤醒标志位置1,表示已经有g被唤醒了,Mutex.state后三位010new = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {                // 唤醒锁的等待队列头部的一个g                // 并把g放到p的funq尾部runtime_Semrelease(&m.sema, false, 1)return}old = m.state}} else {// 锁处在饥饿模式下,直接唤醒锁的等待队列头部的一个g//因为在饥饿模式下没人跟刚被唤醒的g抢锁,所以不用设置锁的唤醒标志位runtime_Semrelease(&m.sema, true, 1)}}

runtime_Semrelease(&m.sema, false, 1) 用来释放mutex等待队列上的一个g

//go:linkname sync_runtime_Semrelease sync.runtime_Semreleasefunc sync_runtime_Semrelease(addr *uint32, handoff bool, skipframes int) {semrelease1(addr, handoff, skipframes)}

semrelease1(addr, handoff, skipframes) 参数handoff若为true,则让被唤醒的g立刻继承当前g的时间片继续执行。若handoff为false,则把刚被唤醒的g放到当前p的runq中。

“Golang中sync.Mutex源码分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

您可能感兴趣的文档:

--结束END--

本文标题: Golang中sync.Mutex源码分析

本文链接: https://www.lsjlt.com/news/351667.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Golang中sync.Mutex源码分析
    本篇内容介绍了“Golang中sync.Mutex源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Mutex结构type M...
    99+
    2023-07-05
  • Golang中Slice使用源码分析
    本文小编为大家详细介绍“Golang中Slice使用源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang中Slice使用源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1、slice结构体首...
    99+
    2023-07-05
  • Golang源码分析之golang/sync之singleflight
    目录1.背景1.1. 项目介绍1.2.使用方法2.源码分析2.1.项目结构2.2.数据结构2.3.API代码流程3.总结1.背景 1.1. 项目介绍 golang/sync库拓展了官...
    99+
    2022-11-13
    golang/sync golang源码分析 golang singleflight
  • Golang HTTP编程源码分析
    这篇文章主要介绍“Golang HTTP编程源码分析”,在日常操作中,相信很多人在Golang HTTP编程源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang H...
    99+
    2023-07-05
  • Golang中Slice的分析与使用源码解析
    目录1、slice结构体2、slice初始化3、append操作4、slice截取5、slice深拷贝6、值传递还是引用传递参考文献1、slice结构体 首先我们来看一段代码 pac...
    99+
    2023-03-09
    go slice使用 go slice
  • Golang Mutex互斥锁源码分析
    目录前言Mutex 特性数据结构Lock()Unlock()前言 在上一篇文章中,我们一起学习了如何使用 Go 中的互斥锁 Mutex,那么本篇文章,我...
    99+
    2024-04-02
  • Golang通道channel的源码分析
    目录前言channel基础结构channel初始化channel发送channel接收小结前言 channel是golang中标志性的概念之一,很好很强大! channel(通道),...
    99+
    2022-12-08
    Golang通道channel Golang通道 Golang channel
  • golang错误捕获源码分析
    这篇文章主要介绍“golang错误捕获源码分析”,在日常操作中,相信很多人在golang错误捕获源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”golang错误捕获源码分析”的疑惑有所帮助!接下来,请跟...
    99+
    2023-07-06
  • Golang通道channel使用源码分析
    这篇文章主要介绍了Golang通道channel使用源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang通道channel使用源码分析文章都会有所收获,下面我们一起来看看吧。前言channel是g...
    99+
    2023-07-04
  • golang中sync.Mutex的实现方法
    目录mutex 的实现思想golang 中 mutex 的实现思想mutex 的结构以及一些 const 常量值Mutex 没有被锁住,第一个协程来拿锁Mutex 仅被协程 A 锁住...
    99+
    2024-04-02
  • GoLang OS包及File类型源码分析
    这篇文章主要介绍“GoLang OS包及File类型源码分析”,在日常操作中,相信很多人在GoLang OS包及File类型源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”GoLan...
    99+
    2023-07-05
  • Golang内存模型实例源码分析
    这篇文章主要介绍“Golang内存模型实例源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Golang内存模型实例源码分析”文章能帮助大家解决问题。1. 简介(Introduction)Go ...
    99+
    2023-07-05
  • GoLang string与strings.Builder使用源码对比分析
    本文小编为大家详细介绍“GoLang string与strings.Builder使用源码对比分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“GoLang string与strings.Builder使用源码对比分析...
    99+
    2023-07-05
  • java 中Buffer源码的分析
    java 中Buffer源码的分析BufferBuffer的类图如下:除了Boolean,其他基本数据类型都有对应的Buffer,但是只有ByteBuffer才能和Channel交互。只有ByteBuffer才能产生Direct的buffe...
    99+
    2023-05-31
    java buffer源码 buf
  • CesiumJS源码分析
    这篇文章主要介绍“CesiumJS源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“CesiumJS源码分析”文章能帮助大家解决问题。1. 有什么光CesiumJS 支持的光的类型比较少,默认场...
    99+
    2023-07-06
  • JDK1.8中的ConcurrentHashMap源码分析
     一、容器初始化 1、源码分析 在jdk8的ConcurrentHashMap中一共有5个构造方法,这四个构造方法中都没有对内部的数组做初始化, 只是对一些变量的初始值做...
    99+
    2024-04-02
  • SocketServer 源码分析
    Creating network servers. contents SocketServer.py contents file head BaseServer BaseServer.serve_forever BaseServ...
    99+
    2023-01-31
    源码 SocketServer
  • Java中的CyclicBarrier源码分析
    这篇文章主要介绍了Java中的CyclicBarrier源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java中的CyclicBarrier源码分析文章都会有所收获,下面我们一起来看看吧。CyclicB...
    99+
    2023-06-30
  • Python中的jieba源码分析
    本篇内容主要讲解“Python中的jieba源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python中的jieba源码分析”吧!前言jieba分词是Python 里面几个比较流行的中文...
    99+
    2023-06-02
  • RateLimiter 源码分析
    俗话说得好,缓存,限流和降级是系统的三把利剑。刚好项目中每天早上导出数据时因调订单接口频率过高,订单系统担心会对用户侧的使用造成影响,让我们对调用限速一下,所以就正好用上了。 常用的限流算法有2种:漏桶算法和令牌桶算法。漏桶算法漏...
    99+
    2023-05-31
    ratelimiter 源码 mi
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作