首页 > 其他分享 >golang mutex原理

golang mutex原理

时间:2024-08-25 13:17:06浏览次数:3  
标签:rw goroutine 饥饿 golang 读锁 race mutex 原理

最近面试遇到问锁的问题,答得不是很好,重新做一下总结梳理

 go中的sync包提供了两种锁的类型,分别是互斥锁sync.Mutex和读写锁sync.RWMutex,这两种锁都属于悲观锁

饥饿模式与正常模式

在下面的内容会经常涉及到一个概念,饥饿模式,这里先简单说一下

1. 正常模式(非公平锁)

正常模式下,所有等待的 goroutine 按照先进先出的顺序等待。唤醒的 goroutine 不会直接拥有锁,而是和新请求锁的 goroutine 竞争锁。新请求的 goroutine 更容易获取锁,因为他在 CPU 上执行,而被唤醒的goroutine 需要重新获取CPU权限,再进行竞争。如此进行下去,就会导致 goroutine 长时间不能获取到锁。

2.饥饿模式(公平锁)

饥饿模式下,维持一个队列,所有的 goroutine 不再竞争,直接把锁交给队列中排在第一位的 goroutine;同时,饥饿模式下,新进来的 goroutine 不会进入到自旋状态,直接放在等待队列的尾部。饥饿模式的触发条件:

  • 一个 goroutine 等待锁唤醒的超过1ms;

  • 当前队列只剩下一个 goroutine 的时候,mutex 切换到饥饿模式。

sync.Mutex

提供方法

提供了三个方法:

  • Lock():进行加锁操作,在同一个goroutine中必须在锁释放之后才能进行再次上锁,不然会panic

  • Unlock(): 进行解锁操作,如果这个时候未加锁会panic,mutex和goroutine不关联,也就是说对于mutex的加锁解锁操作可以发生在多个goroutine间

  • tryLock():用得少,尝试获取锁,获取成功返回true,否则返回false,返回false可能有几种情况

    • 当锁被其他goroutine占有,无法获取,将立刻返回false,

    • 锁处于饥饿模式,此时要让饥饿队列先获取锁,将立刻返回false,

    • 当锁可用时尝试获取锁,获取失败也返回false

底层数据结构

底层就两个变量,分别说明锁的状态,以及一个信号量,用于唤醒等待goroutine

type Mutex struct {
   state int32        // 表示当前互斥锁的状态,复合型字段
   sema  uint32        // 信号量变量,用来控制等待goroutine的阻塞休眠和唤醒
}

state 是复合字段,不止说明了是否上锁,还说明了锁的模式,等待goroutine的数量等

const (
   mutexLocked = 1 << iota // mutex is locked
   mutexWoken
   mutexStarving
   mutexWaiterShift = iota
}
  • mutexLocked**:是否被锁定,由于 iota 的初始值是 0,因此 mutexLocked 的值为 1 << 0,即 1(二进制为 0001)。

  • mutexWoken**:这一位被设置时,表示有一个等待的 Goroutine 已经被唤醒或正在被唤醒。这个状态用于避免不必要地唤醒多个 Goroutine。值为 1 << 1**,即 2**(二进制为 0010**)

  • mutexStarving**:这一位被设置时,表示 Mutex 处于“饥饿模式”,这在某些特定条件下会被触发,下文将说到,值为 1 << 2,即 4(二进制为 0100

  • mutexWaiterShift**:表示等待获取锁的goroutine数量,从偏移量4开始计算

加锁过程

通过底层的atomic包中提供的CAS方法修改锁的状态,

  • 如果锁的状态是0,则改变其状态,然后直接返回

  • 否则进入 Slow path,看是否自旋或者在饥饿场景下获取锁

func (m *Mutex) Lock() {
   // Fast path: grab unlocked mutex.
   if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
      if race.Enabled {
         race.Acquire(unsafe.Pointer(m))
      }
      return
   }
   // Slow path (outlined so that the fast path can be inlined)
   m.lockSlow()
}

进入lockSlow函数后,会发现有一个for循环,这个时候实际上就是在自旋获取锁, 即:乐观的认为当前正在持有锁的g能在短时间内归还锁,所以需要一些条件来判断:到底能不能短时间归还

主要工作就是:

  1. 判断锁是否是饥饿状态,不是则自旋(即进行此次循环),否则直接进入队列。

  2. **如果自旋过程中等待时间过长,让锁自动变为饥饿模式**(网上说的1ms,没找到源码具体体现在哪)

下面贴部分代码,注释写的很详细,没截下来的地方,才是自旋,下面这部分图只是在判断某些条件

总结

mutex底层实际上就是一个状态和信号量,信号量用于通知goroutine,状态用于判断是否加锁。同时状态也是个复合变量,还记录了锁的模式(正常,饥饿模式),等待锁的goroutine数量。

获取锁就是通过atomic的CAS方法修改状态,修改失败则进入自旋,长时间获取不到锁自动转为饥饿模式

sync.RWMutex

读写锁是为了在读写并发的场景下提高锁的效率而使用,分为读锁和写锁

  • 读锁:读数据时添加,与其他读锁可以共享

  • 写锁:写数据时添加,与读锁,写锁互斥。也就是加写锁时需要保证既没有读锁,也没有写锁

底层数据结构

RWMutex 底层也是基于mutex实现的,并包含了多个字段,这些字段在控制锁的行为时起着关键作用。下面是每个字段的含义:

type RWMutex struct {
   w           Mutex        // 当 Goroutine 进行写操作时,上锁
   writerSem   uint32       // 写信号量,有读锁存在时,阻塞写操作,并在所有读锁释放后唤醒写操作。
   readerSem   uint32       // 读信号量,有写锁存在时,阻塞读操作,并在写锁释放后唤醒读操作。
   readerCount atomic.Int32 // 当前持有读锁的 goroutine 数量,0时才能获取写锁
   readerWait  atomic.Int32 // 正在等待的读 goroutine 数量,锁正在写操作时有值
}

提供方法

  • RLock()/RUnlock():加读锁,释放读锁。在没有写锁时才能成功加锁

  • Lock()/Unlock():加写锁,需要保证readerCount=0w.state=0才能成功上锁

加锁过程

RLock:直接增加readerCount数量
func (rw *RWMutex) RLock() {
   // 保证w未被上锁
   if race.Enabled {
      _ = rw.w.state
      race.Disable()
   }
   // 使用atomic.Add,为读锁持有数量+1
   if rw.readerCount.Add(1) < 0 {
      // A writer is pending, wait for it.
      runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
   }
   if race.Enabled {
      race.Enable()
      race.Acquire(unsafe.Pointer(&rw.readerSem))
   }
}
Lock():底层还是调用的mutex.Lock()方法,不过需要判断此时是否有读锁
func (rw *RWMutex) Lock() {
    // 保证w未被上锁
   if race.Enabled {
      _ = rw.w.state
      race.Disable()
   }
   // First, resolve competition with other writers.
   rw.w.Lock()
   // Announce to readers there is a pending writer.
   // 判断读锁数量
   r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
   // Wait for active readers.
   if r != 0 && rw.readerWait.Add(r) != 0 {
      runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
   }
   if race.Enabled {
      race.Enable()
      race.Acquire(unsafe.Pointer(&rw.readerSem))
      race.Acquire(unsafe.Pointer(&rw.writerSem))
   }
}

 

 

标签:rw,goroutine,饥饿,golang,读锁,race,mutex,原理
From: https://www.cnblogs.com/hackcaixukun/p/18378855

相关文章

  • 现代Web开发中AJAX请求的运作原理
    ajax的请求过程1、新建ajax对象:    IE6不兼容newXMLHttpRequest();    IE6下,ajax对象的兼容方法:        try判断的方法:          varxhr=null;            try{    xhr=newXMLHttpRequest();    }      ......
  • 【计算机组成原理】2.2.3_2 无符号数的加减运算
    2.2.3_2无符号数的加减运算00:00各位同学大家好,在这个视频中我们会探讨无符号数的加减运算用计算机是怎么实现的。在王道书当中重点探讨了有符号数补码的加减运算怎么实现。对于无符号数的加减运算,王道书当中并没有深入的探讨。所以这个视频是对王道书的一个补充。大家可......
  • (javaweb)springboot的底层原理
    目录一.配置优先级二.Bean的管理1.获取bean​编辑​编辑2.bean作用域3.第三方bean三.SpringBoot原理 自动配置原理原理分析:conditional: 自动配置案例:(自定义starter分析)总结一.配置优先级//命令行参数的优先级最高二.Bean的管理1.获取bean注入ioc......
  • 科普文:软件架构Nginx系列之【Nginx 核心架构设计和原理】
    概叙Nginx是什么Nginx(engineX)是一个开源的轻量级的HTTP服务器,能够提供高性能的HTTP和反向代理服务。与传统的Apache服务器相比,在性能上Nginx占用系统资源更小、支持高并发,访问效率更高;在功能上,Nginx不仅作为Web服务软件,还适用于反向代理、负载均衡等场景;在安装配置上,Nginx......
  • OSPF路由原理详解与关键点
    目录一. OSPF简介:二. OSPF原理描述:三.  OSPF的核心内容: 四. OSPF的邻居关系和邻接五.LSA在各区域中传播的支持情况一. OSPF简介:开放式最短路径优先OSPF(OpenShortestPathFirst)是IETF组织开发的一个基于链路状态的内部网关协议(InteriorGatewayProt......
  • 山东大学计算机组成原理实验6七段译码设计(含原理图,引脚分配,实验结果输入输出)
    实验目的熟悉QuartusII的设计流程全过程,学习计数器的设计和硬件测试。掌握原理图的设计方法。实验原理4位计数器连接7段译码,多数码管进行显示控制。实验框图如图6所示。图6 原理图示意图其中,CNT4B采用74161计数器芯片实现,DECL7S采用7448(共阳)设计。实验内容(1)设计工程......
  • MySQL索引底层实现原理
    索引的本质MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。我们知道,数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。最......
  • DDD是软件工程的第一性原理?
    本文书接上回《DDD建模后写代码的正确姿势》,关注公众号(老肖想当外语大佬)获取信息:最新文章更新;DDD框架源码(.NET、Java双平台);加群畅聊,建模分析、技术实现交流;视频和直播在B站。前提本文需要以系列前文的逻辑链条和结论为前提,如果没有阅读过前文的,可以阅读合集《老肖......
  • [底层原理] C/C++获取时间(将时间戳转换为年月日)?
    前言大家都知道,计算机中存储的时间是一个整数,在现在的编程语言中,可以很方便地将时间戳(整数)转换为字符串,但是如果没有这些我们该如何自己计算出呢?刚好以前研究过Nginx的源代码,我以他的代码为例,说明其背后的数学原理。当然在工程实践中,没有必要花时间自己实现转换的函数,所以本......
  • 【计算机组成原理】2.2.2 定点数的移位运算
    2.2.2定点数的移位运算00:00这一小节中我们来学习定点数的移位运算怎么实现。移位运算又可以进一步的划分为算术移位、逻辑移位还有循环移位。我们会按从上至下的顺序依次讲解。00:13好,首先来认识一下什么叫做算术移位。我们从大家熟悉的十进制数出发,假设这儿有这样的......