iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >如何理解Go里面的互斥锁mutex
  • 727
分享到

如何理解Go里面的互斥锁mutex

2023-06-19 09:06:29 727人浏览 八月长安
摘要

如何理解Go里面的互斥锁mutex,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. 锁的基础概念1.1 CAS与轮询1.1.1 cas实现锁 在锁的实现中现在越来越多的采用C

如何理解Go里面的互斥mutex,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

1. 锁的基础概念

1.1 CAS与轮询

1.1.1 cas实现锁

如何理解Go里面的互斥锁mutex 在锁的实现中现在越来越多的采用CAS来进行,通过利用处理器的CAS指令来实现对给定变量的值交换来进行锁的获取

1.1.2 轮询锁

如何理解Go里面的互斥锁mutex多线程并发的情况下很有可能会有线程CAS失败,通常就会配合for循环采用轮询的方式去尝试重新获取锁

1.2 锁的公平性

如何理解Go里面的互斥锁mutex 锁从公平性上通常会分为公平锁和非公平锁,主要取决于在锁获取的过程中,先进行锁获取的线程是否比后续的线程更先获得锁,如果是则就是公平锁:多个线程按照获取锁的顺序依次获得锁,否则就是非公平性

1.3 饥饿与排队

1.3.1 锁饥饿

锁饥饿是指因为大量线程都同时进行获取锁,某些线程可能在锁的CAS过程中一直失败,从而长时间获取不到锁

1.3.2 排队机制

如何理解Go里面的互斥锁mutex 上面提到了CAS和轮询锁进行锁获取的方式,可以发现如果已经有线程获取了锁,但是在当前线程在多次轮询获取锁失败的时候,就没有必要再继续进行反复尝试浪费系统资源,通常就会采用一种排队机制,来进行排队等待

1.4 位计数

在大多数编程语言中针对实现基于CAS的锁的时候,通常都会采用一个32位的整数来进行锁状态的存储

2. mutex实现

2.1 成员变量与模式

2.1.1 成员变量

在go的mutex中核心成员变量只有两个state和sema,其通过state来进行锁的计数,而通过sema来实现排队

type Mutex struct {state int32sema  uint32}

2.1.2 锁模式

锁模式主要分为两种


描述公平性
正常模式正常模式下所有的goroutine按照FIFO的顺序进行锁获取,被唤醒的goroutine和新请求锁的goroutine同时进行锁获取,通常新请求锁的goroutine更容易获取锁
饥饿模式饥饿模式所有尝试获取锁的goroutine进行等待排队,新请求锁的goroutine不会进行锁获取,而是加入队列尾部等待获取锁

上面可以看到其实在正常模式下,其实锁的性能是最高的如果多个goroutine进行锁获取后立马进行释放则可以避免多个线程的排队消耗 同理在切换到饥饿模式后,在进行锁获取的时候,如果满足一定的条件也会切换回正常模式,从而保证锁的高性能

2.2 锁计数

2.2.1 锁状态

如何理解Go里面的互斥锁mutex 在mutex中锁有三个标志位,其中其二进制位分别位001(mutexLocked)、010(mutexWoken)、100(mutexStarving), 注意这三者并不是互斥的关系,比如一个锁的状态可能是锁定的饥饿模式并且已经被唤醒

mutexLocked = 1 << iota // mutex is lockedmutexWokenmutexStarving

2.2.2 等待计数

如何理解Go里面的互斥锁mutex

mutex中通过低3位存储了当前mutex的三种状态,剩下的29位全部用来存储尝试正在等待获取锁的goroutine的数量

mutexWaiterShift = iota // 3

2.3唤醒机制

2.3.1 唤醒标志

如何理解Go里面的互斥锁mutex 唤醒标志其实就是上面说的第二位,唤醒标志主要用于标识当前尝试获取goroutine是否有正在处于唤醒状态的,记得上面公平模式下,当前正在cpu上运行的goroutine可能会先获取到锁

2.3.2 唤醒流程

如何理解Go里面的互斥锁mutex 当释放锁的时候,如果当前有goroutine正在唤醒状态,则只需要修改锁状态为释放锁,则处于woken状态的goroutine就可以直接获取锁,否则则需要唤醒一个goroutine, 并且等待这个goroutine修改state状态为mutexWoken,才退出

2.4 加锁流程

如何理解Go里面的互斥锁mutex

2.3.1 快速模式

如果当前没有goroutine加锁,则并且直接进行CAS成功,则直接获取锁成功

// Fast path: grab unlocked mutex.if atomic.CompareAndSwapint32(&m.state, 0, mutexLocked) {if race.Enabled {race.Acquire(unsafe.Pointer(m))}return}

2.3.2 自旋与唤醒

// 注意这里其实包含两个信息一个是如果当前已经是锁定状态,然后允许自旋iter主要是计数次数实际上只允许自旋4次// 其实就是在自旋然后等待别人释放锁,如果有人释放锁,则会立刻进行下面的尝试获取锁的逻辑if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {// !awoke 如果当前线程不处于唤醒状态// old&mutexWoken == 0如果当前没有其他正在唤醒的节点,就将当前节点处于唤醒的状态// old>>mutexWaiterShift != 0 :右移3位,如果不位0,则表明当前有正在等待的goroutine// atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken)设置当前状态为唤醒状态if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true}// 尝试自旋,runtime_doSpin()// 自旋计数iter++        // 从新获取状态old = m.statecontinue}

2.3.3 更改锁状态

流程走到这里会有两种可能: 1.锁状态当前已经不是锁定状态 2.自旋超过指定的次数,不再允许自旋了

new := oldif old&mutexStarving == 0 {// 如果当前不是饥饿模式,则这里其实就可以尝试进行锁的获取了|=其实就是将锁的那个bit位设为1表示锁定状态new |= mutexLocked}if old&(mutexLocked|mutexStarving) != 0 {// 如果当前被锁定或者处于饥饿模式,则增等待一个等待计数new += 1 << mutexWaiterShift}if starving && old&mutexLocked != 0 {// 如果当前已经处于饥饿状态,并且当前锁还是被占用,则尝试进行饥饿模式的切换new |= mutexStarving}if awoke {if new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}// awoke为true则表明当前线程在上面自旋的时候,修改mutexWoken状态成功// 清除唤醒标志位            // 为什么要清除标志位呢?            // 实际上是因为后续流程很有可能当前线程会被挂起,就需要等待其他释放锁的goroutine来唤醒            // 但如果unlock的时候发现mutexWoken的位置不是0,则就不会去唤醒,则该线程就无法再醒来加锁new &^= mutexWoken}

2.3.3 加锁排队与状态转换

再加锁的时候实际上只会有一个goroutine加锁CAS成功,而其他线程则需要重新获取状态,进行上面的自旋与唤醒状态的重新计算,从而再次CAS

if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {// 如果原来的状态等于0则表明当前已经释放了锁并且也不处于饥饿模式下                // 实际的二进制位可能是这样的 1111000, 后面三位全是0,只有记录等待goroutine的计数器可能会不为0                // 那就表明其实break // locked the mutex with CAS}// 排队逻辑,如果发现waitStatrTime不为0,则表明当前线程之前已经再排队来,后面可能因为            // unlock被唤醒,但是本次依旧没获取到锁,所以就将它移动到等待队列的头部queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}            // 这里就会进行排队等待其他节点进行唤醒runtime_SeMacquireMutex(&m.sema, queueLifo)// 如果等待超过指定时间,则切换为饥饿模式 starving=true            // 如果一个线程之前不是饥饿状态,并且也没超过starvationThresholdNs,则starving为false            // 就会触发下面的状态切换starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs// 重新获取状态            old = m.stateif old&mutexStarving != 0 {                 // 如果发现当前已经是饥饿模式,注意饥饿模式唤醒的是第一个goroutine                // 当前所有的goroutine都在排队等待// 一致性检查,if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}// 获取当前的模式delta := int32(mutexLocked - 1<<mutexWaiterShift)if !starving || old>>mutexWaiterShift == 1 {// 如果当前goroutine不是饥饿状态,就从饥饿模式切换会正常模式                    // 就从mutexStarving状态切换出去delta -= mutexStarving}                // 最后进行cas操作atomic.AddInt32(&m.state, delta)break}            // 重置计数awoke = trueiter = 0} else {old = m.state}

2.5 释放锁逻辑

如何理解Go里面的互斥锁mutex

2.5.1 释放锁代码

func (m *Mutex) Unlock() {if race.Enabled {_ = m.staterace.Release(unsafe.Pointer(m))}// 直接进行cas操作new := atomic.AddInt32(&m.state, -mutexLocked)if (new+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}if new&mutexStarving == 0 {// 如果释放锁并且不是饥饿模式old := newfor {if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {// 如果已经有等待者并且已经被唤醒,就直接返回return}// 减去一个等待计数,然后将当前模式切换成mutexWokennew = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {// 唤醒一个goroutineruntime_Semrelease(&m.sema, false)return}old = m.state}} else {// 唤醒等待的线程runtime_Semrelease(&m.sema, true)}}

看完上述内容,你们掌握如何理解Go里面的互斥锁mutex的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注编程网精选频道,感谢各位的阅读!

--结束END--

本文标题: 如何理解Go里面的互斥锁mutex

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

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

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

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

下载Word文档
猜你喜欢
  • 如何理解Go里面的互斥锁mutex
    如何理解Go里面的互斥锁mutex,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. 锁的基础概念1.1 CAS与轮询1.1.1 cas实现锁 在锁的实现中现在越来越多的采用C...
    99+
    2023-06-19
  • 如何理解Linux驱动中内核互斥锁
    如何理解Linux驱动中内核互斥锁,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。 互斥体概述信号量是在并行处理环境中对多个处理器访问某个公共资源进行保护的机制,mut...
    99+
    2023-06-15
  • 如何理解Go运行时中的Mutex
    这篇文章主要讲解了“如何理解Go运行时中的Mutex”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何理解Go运行时中的Mutex”吧!sync.Mutex...
    99+
    2022-10-19
  • 如何理解互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景
    本篇内容主要讲解“如何理解互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何理解互斥锁、自旋锁、读写锁、悲观锁、...
    99+
    2022-10-18
  • 如何理解Go里面的sync.Map
    本篇文章为大家展示了如何理解Go里面的sync.Map,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。基础筑基在大多数语言中原始map都不是一个线程安全的数据结构,那如果要在多个线程或者gorouti...
    99+
    2023-06-19
  • 如何理解golang里面的读写锁实现与核心原理
    如何理解golang里面的读写锁实现与核心原理,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。基础筑基读写锁的特点读写锁区别与互斥锁的主要区别就是读锁之间是共享的...
    99+
    2023-06-19
  • 如何在面试中展示你对Go和Linux的理解?
    在当前的技术行业中,越来越多的公司开始使用Go语言和Linux系统进行开发和部署。因此,如果你想在这个领域找到一份工作,对于这两个技术的深入理解是非常重要的。在面试过程中,你需要展示你对Go和Linux的理解,让面试官相信你是一位合适的候选...
    99+
    2023-08-22
    linux 响应 面试
  • 如何在JavaScript面试中展示对Go语言并发的理解?
    在现代的软件开发领域,面试已经成为了招聘流程中不可或缺的一部分。在JavaScript面试中,对Go语言并发的理解是一项重要的技能,而如何在面试中展示这种理解则是一项挑战。在本文中,我们将介绍如何在JavaScript面试中展示对Go语言并...
    99+
    2023-06-23
    并发 javascript 面试
  • 面试中如何展现你对Go接口和函数的理解?
    在Go语言中,接口(interface)是一种类型,它定义了一组方法的签名。接口类型可以定义为一个变量的类型,因此它可以被用来声明变量、作为函数参数或返回值。在Go语言中,接口是非常重要的,因为它允许我们编写可复用的代码,同时还可以减少代...
    99+
    2023-06-16
    接口 函数 面试
  • GO语言学习笔记:如何在面试中展示您对Laravel的理解?
    在现代Web开发中,Laravel是一个非常受欢迎的PHP框架。但是,如果您是一名GO语言开发人员,您可能会想知道如何在面试中展示您对Laravel的理解。在本文中,我们将探讨一些GO语言开发人员可以使用的技巧,以便在面试中展示对Lara...
    99+
    2023-10-24
    学习笔记 面试 laravel
  • 如何在面试中展示你对Go和NumPy文件操作的深入理解?
    在现代的软件工程中,Go和NumPy是两个非常流行的编程语言。其中,Go通常用于构建高效且并发性能较好的Web应用程序,而NumPy则用于科学计算和数据分析。如果你正在寻找一份与这两种语言相关的工作,那么你需要了解如何在面试中展示你对Go和...
    99+
    2023-11-13
    numy 文件 面试
  • 如何在Go语言面试中展示你对二维码和索引的理解?
    在Go语言面试中,展示自己对于二维码和索引的理解是非常重要的。这两个概念是在Go语言编程中经常用到的,因此掌握它们对于一个程序员来说是非常必要的。 二维码是一种矩阵式的条码,它可以存储大量的信息,并且可以被快速扫描读取。在Go语言中,我们可...
    99+
    2023-07-03
    二维码 索引 面试
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作