Go 源码之互斥锁 Mutex

文章目录

  • 一、总结
  • 二、源码
    • (一)Mutex
    • (二) Lock
    • (三)Unlock
  • 三、常见问题
          • 有劳各位看官 `点赞、关注➕收藏 `,你们的支持是我最大的动力!!!
          • 接下来会不断更新 `golang` 的一些`底层源码及个人开发经验`(个人见解)!!!
          • 同时也欢迎大家在评论区提问、分享您的经验和见解!!!

一、总结

  • 锁不可复制:拷贝互斥锁同时会拷贝锁的状态,容易造成死锁

  • 不是可重入锁,并且一个协程上锁,可以由另外一个协程解锁

  • mutex 锁结构

    • state:32位,锁状态,bitmap 设计,
      • 1 mutexLocked :低1位 锁定状态
      • 2 mutexWoken :低2位,从正常模式被唤醒
      • 3 mutexStarving 是低3位,进入饥饿模式
        1. mutexWaiterShift 剩下 29 位,当前互斥锁上等待者的数量
    • sema:协程等待信号量,用于控制goroutine的阻塞与唤醒
  • 上锁
    image.png

  • 解锁
    image.png

  • 锁的两种模式

  • 正常模式
    在正常模式下等待的 g 按照先进先出的方式获取锁
    新 g 会 自旋 ,并且和刚唤醒的 g 竞争锁,新 g 会优先获得锁,会导致刚被唤起的 g 一直获取不到锁,
    这种情况的出现会导致线程长时间被阻塞下去,所以Go语言在1.9中进行了优化,引入了 饥饿模式
  • 饥饿模式
    为了解决等待 goroutine 队列的长尾问题(饿死)
    当 g 超过 1ms 没有获取到锁,就会将当前互斥锁切换到饥饿模式
    等待的 g 按照先进先出的方式获取锁
    饥饿模式下,新进来的 G 不会参与抢锁也不会进入自旋状态,会直接进入等待队列的尾部。
    在这种情况下,这个被唤醒的 goroutine 会优先加入到等待队列的前面,防止饿死
    如果一个 goroutine 获得了互斥锁并且它在队列的末尾或者它等待的时间少于 1ms,那么当前的互斥锁就会切换回正常模式

二、源码

(一)Mutex

const (mutexLocked = 1 << iota 									// 1 mutex 锁定状态mutexWoken 													// 2 mutex 从正常模式被唤醒mutexStarving 												// 4 mutex进入饥饿状态mutexWaiterShift = iota 									// 3 当前互斥锁上等待者的数量
)type Mutex struct { 												// Mutex 不可被复制state int32 // 32位,锁状态,bitmap 设计,低三位表示锁的状态,剩下 29 位表当前互斥锁上等待者的数量sema  uint32 // 缓冲信号量,用来控制等待goroutine的阻塞休眠和唤醒,可以理解为一个队列
}

image.png

(二) Lock

  • 直接 CAS 进行原子操作上锁,成功则返回,失败则执行 lockSlow()
  • 上锁失败,执行 lockSlow(),内部持续 for 循环
    • 支持自旋(正常模式、cpu空闲、自旋次数<4),则进入自旋
    • 不支持自旋:两种模式
      • 正常模式:加入尾部队列,按照先进先出的方式加入队列等待获取锁
      • 饥饿模式:当goroutine超过1ms没有获取到锁,就会将当前互斥锁切换到饥饿模式,如果当前goroutine 存在队列中,则移动到队头,然后按照先进先出的方式获取锁,防止饿死

func (m *Mutex) Lock() {if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { 	// 直接 CAS 修改锁的状态,将 state=0 改为 1 return}m.lockSlow()															// CAS 无法直接上锁,则执行慢路径
}
func (m *Mutex) lockSlow() {var waitStartTime int64 											// 用来计算waiter的等待时间starving := false 													// 是否是饥饿模式awoke := false 														// 是否唤醒iter := 0 															// 自旋次数old := m.state 														// 旧的锁状态for {// 支持自旋:锁不是饥饿模式 && cpu 支持继续自旋(<=4次)if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {// g不是唤醒状态 && // 没有其他正在唤醒的goroutine && // 等待队列中有正在等待的goroutine// && 尝试将当前锁的低2位的Woken状态位设置为1,表示已被唤醒, 这是为了通知在解锁Unlock()中不再唤醒其他waiterif !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true                                     // 设置当前goroutine唤醒成功}runtime_doSpin()                                    // 进行自旋iter++                                              // 自旋次数++old = m.state                                       //更新锁状态continue}new := oldif old&mutexStarving == 0 { new |= mutexLocked                          // 非饥饿模式下进行加锁}if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift                // 等待着数量+1}if starving && old&mutexLocked != 0 {new |= mutexStarving	                    // 加锁的情况下切换为饥饿模式}if awoke {                                     // goroutine 唤醒的时候进行重置标志if new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}new &^= mutexWoken}if atomic.CompareAndSwapInt32(&m.state, old, new) {     //设置新的状态if old&(mutexLocked|mutexStarving) == 0 {break }queueLifo := waitStartTime != 0if waitStartTime == 0 {                       // 判断是不是第一次加入队列waitStartTime = runtime_nanotime()         // 如果之前就在队列里面等待了,加入到对头}        runtime_SemacquireMutex(&m.sema, queueLifo, 1) // 阻塞等待// 检查锁是否处于饥饿状态starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = m.state// 如果锁处于饥饿状态,直接抢到锁if old&mutexStarving != 0 {if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}//设置标志,进行加锁并且waiter-1delta := int32(mutexLocked - 1<<mutexWaiterShift)//如果是最后一个的话清除饥饿标志if !starving || old>>mutexWaiterShift == 1 {//退出饥饿模式				delta -= mutexStarving}atomic.AddInt32(&m.state, delta)break}awoke = trueiter = 0} else {old = m.state}}
}

(三)Unlock

  • 直接 CAS 进行原子操作解锁,成功则返回,失败则执行 unlockSlow()
  • 解锁失败,执行 unlockSlow()
    • 正常模式
      • 如果当前队列中没有waiter,只有自己本身,直接解锁返回
      • 如果当前队列中有waiter,解锁后唤醒下个等待者 runtime_Semrelease(&m.sema, false, 1)
    • 饥饿模式
      • 饥饿模式直接将锁的控制权交给队列中队头等待的waiter
func (m *Mutex) Unlock() {new := atomic.AddInt32(&m.state, -mutexLocked)  // 直接 CAS 修改锁的状态 if new != 0 {// 不等于0说明解锁失败,m.unlockSlow(new)}
}
func (m *Mutex) unlockSlow(new int32) {//解锁一个未加锁的Mutex会报错if (new+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}if new&mutexStarving == 0 {old := newfor {// 正常模式下,没有waiter或者在处理事情的情况下直接返回if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}//如果有等待者,设置mutexWoken标志,waiter-1,更新statenew = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {runtime_Semrelease(&m.sema, false, 1)return}old = m.state}} else {// 饥饿模式下会直接将mutex交给下一个等待的waiter,让出时间片,以便waiter执行runtime_Semrelease(&m.sema, true, 1)}
}

三、常见问题

1. sema 字段的含义作用
在正常模式下,一个goroutine先通过自旋方式获得锁,如果还不能获取锁,则通过信号量进行排队等

(所有等待者都会按照先入先出的顺序排队)但是当被唤醒后,第一个等待者并不会立即获得锁,而是需要和那些正在处于自旋阶段,尚未加入到队列中的routine竞争,如果抢不到锁的话,重新插入到队列的头部,而当这个goroutine加锁等待的时间超过了1ms之后,它会把mutex由正常模式切换到饥饿模式,这种模式下锁的所有权直接传递给头部的routine。后来者不会自旋,也不会尝试获取锁,直接加到队列尾部

2.什么是CAS,什么是原子操作
CAS(Compare and Swap)比较并交换,比较两个值,如果他们两者相等就把他们交换。这是一个由CPU硬件提供并实现的原子操作。
原子操作:操作系统提高的锁机制来保证操作的原子性和线程安全性。这种锁机制可以使执行原子操作的 CPU 独占内存总线或者缓存,并防止其他 CPU 对同一内存地址进行读写操作,从而避免了数据竞争的问题
具体来说,在执行原子操作时,CPU 会向内存总线或者缓存发送锁请求信号,然后等待锁授权。一旦锁授权成功,CPU 就可以将操作的结果写入内存,然后释放锁。其他 CPU 在锁被释放之前不能对同一内存地址进行读写操作,从而保证了操作的原子性和线程安全性。
需要注意的是,原子操作增加 CPU 的开销和内存带宽的消耗

3. 锁的正常模式和饥饿模式?
image.png

4.为什么锁不可复制
因为互斥锁没有绑定 gid,复制锁会复制锁的状态,容易出现死锁

5.什么情况下mutex会从饥饿模式变成正常模式呢?
如果当前 goroutine 等待锁的时间超过了 1ms,互斥锁就会切换到饥饿模式。
如果当前 goroutine 是互斥锁最后一个waiter,或者等待的时间小于 1ms,互斥锁切换回正常模式。

5. goroutine能进入自旋的条件

  • 当前互斥锁处于正常模式,不处于饥饿模式
  • 积累的自旋次数小于最大自旋次数(active_spin=4
  • cpu 核数大于 1
  • 有空闲的 P
  • 当前 goroutine 所挂载的 P 下,本地待运行队列为空
//go:linkname sync_runtime_canSpin sync.runtime_canSpin
func sync_runtime_canSpin(i int) bool {// 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
}
有劳各位看官 点赞、关注➕收藏 ,你们的支持是我最大的动力!!!
接下来会不断更新 golang 的一些底层源码及个人开发经验(个人见解)!!!
同时也欢迎大家在评论区提问、分享您的经验和见解!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/793093.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中间件漏洞!!!

上次好像记得讲过了框架漏洞&#xff0c;&#xff08;weblogic不是&#xff09;那么&#xff0c;今天我们就来讲一些中间件的漏洞 1.Apache解析漏洞 众所周知&#xff0c;Apache是一个非常出名的中间件&#xff0c;本来呢&#xff0c;他是不存在漏洞的&#xff0c;但是如果用…

探索大数据时代下与云计算技术融合:实现企业级数据处理与分析的灵活性和效率性

引言&#xff1a; 关联阅读博客文章&#xff1a;深度剖析&#xff1a;计算机集群在大数据体系中的关键角色和技术要点 随着信息时代的到来&#xff0c;数据量的爆炸性增长已成为一种常态。企业、政府、科研机构等各个领域都面临着海量数据的收集、存储、处理和分析的挑战。在…

go并发请求url

sync.WaitGroup写法 package mainimport ("database/sql""fmt""net/http""sync""time"_ "github.com/go-sql-driver/mysql" )func main() {//开始计时start : time.Now()//链接数据库&#xff0c;用户名&#xf…

Flutter-发布插件到pub上传不上问题

问题1&#xff1a; 尝试指令&#xff1a; flutter packages pub publish --serverhttps://pub.dartlang.org问题2&#xff1a; 问题1解决后&#xff0c;进入验证身份&#xff0c;点击终端显示的链接&#xff0c;跳转到google验证&#xff0c;记得这里要科*学上网&#xff0c;点…

基于 Docker 的 python grpc quickstart

工作之后一直使用的 RPC 框架是 Apache 的 thrift&#xff0c;现在发现 grpc 更流行&#xff0c;所以也要学习一下&#xff0c;先来简单的跑一下 demo。在本地安装运行也很方便&#xff0c;不过因为有了 docker&#xff0c;所以在 docker 里面安装运行隔离性更好&#xff0c;顺…

Unity框架,ET框架8.1版本的打包流程记录

目录 打包代码前置1.必须要安装Visusal Studio 2022的组件&#xff0c;如下图&#xff0c;必须都要进行安装&#xff0c;不然会在代码重构的时候报错&#xff0c;丢失SDK。Rider的版本必须2023及以上 步骤一、使用Rider编辑器打开项目后进行重构项目步骤二、使用HybirdCLR生成A…

在ArcGIS Pro中优雅的制作荧光图

最近在网上看到了荧光图&#xff0c;觉得挺帅气&#xff0c;去网上查询了怎么制作荧光图&#xff0c;发现大部分都是QGIS的教程&#xff0c;作为ArcGIS的死忠用户&#xff0c;决定在ArcGIS Pro中实现&#xff0c;其实挺简单的。 1、软件&#xff1a;ArcGIS Pro3.0 2、点数据&a…

NOI - OpenJudge - 2.5基本算法之搜索 - 1490:A Knight‘s Journey - 超详解析(含AC代码)

点赞关注吧~ 1490:A Knights Journey 查看提交统计提问 总时间限制: 1000ms 内存限制: 65536kB 描述 Background The knight is getting bored of seeing the same black and white squares again and again and has decided to make a journey around the world. When…

Vue开发加速器:Chrome的vue-devtools插件解决开发难题

I. 简介 Vue.js是一个流行的前端JavaScript框架&#xff0c;它允许开发人员轻松构建可扩展的Web应用程序和移动应用程序。vue-devtools是一个Chrome浏览器的扩展程序&#xff0c;它是由Vue.js官方维护的一款强大的调试工具。结合Vue.js和vue-devtools插件&#xff0c;开发人员…

前端三剑客 —— CSS (第五节)

目录 内容回顾&#xff1a; 特殊样式 特殊样式 CSS变量 常见函数 倒影效果 页面布局 Table 布局&#xff08;了解即可&#xff09; DIVCSS布局 弹性布局 1&#xff09;不使用弹性布局&#xff0c;而是使用DIVCSS 2&#xff09;使用弹性布局实现导航菜单 内容回顾…

echart 仪表盘实现指针的渐变色及添加图片

需求&#xff1a; 在仪表盘中设置指针为渐变色&#xff0c;并在仪表盘中间添加图片。 实现重点&#xff1a; 1、仪表盘指针渐变色的实现 渐变色通过设置pointer的itemStyle属性内的color实现&#xff0c;重点是echart版本&#xff0c;这个原本使用4.8.0的版本不起作用&#xff…

排序基础---插入排序及在c++中开辟二维数组

排序基础---插入排序 插入排序是一种比较排序。 选出一个临时变量tmp. 然后弄一个end&#xff0c;end最初可以是0. 那么tmp便应该是a[end1] 最终的目的是为了使一个序列有序&#xff0c;所以应该让tmp依次与前[0,end],进行比较最后插入到合适的位置。 void insert_sort(…

AD20全流程的使用笔记

目录 首先一个完整的AD工程文件需要我们自己建立的文件有这些&#xff1a; 新建工程&#xff1a; 从现有的工程文件中将元件添加到原理图库&#xff1a; 元件的摆放&#xff1a; 器件的复制及对齐&#xff1a; 导线、Netlabe、端口的添加&#xff1a; Value值的校对&…

SQL注入---盲注

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.盲注概述 注是一种SQL注入攻击的形式&#xff0c;在这种攻击中&#xff0c;攻击者向目标应用程序发送恶意注入代码&#xff0c;然后通过观察应用程序的响应来推断出数据库中的信息。与常规的…

设计模式——抽象工厂模式02

如果是工厂模式是对同一类商品进行抽象然后生产。 那么抽象工厂模式是对工厂的抽象&#xff0c;每个工厂都能生产多种产品&#xff0c;不同工厂生产的商品性质相同&#xff0c;但外观&#xff0c;品牌会略有差异。 设计模式&#xff0c;一定要敲代码理解 商品抽象 public in…

每日五道java面试题之ZooKeeper篇(一)

目录&#xff1a; 第一题. ZooKeeper 是什么&#xff1f;第二题. Zookeeper 文件系统第三题. Zookeeper 怎么保证主从节点的状态同步&#xff1f;第四题. 四种类型的数据节点 Znode第五题 . Zookeeper Watcher 机制 – 数据变更通知 第一题. ZooKeeper 是什么&#xff1f; Zoo…

MySQL面试题系列-6

MySQL是一个关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的RDBMS (Relational Database Management System&#xff0c;关系数据…

flutter项目ffi相关

Flutter 使用FFICustomPainter实现全平台渲染视频_flutter ffi-CSDN博客

libusb Qt使用记录

1.libusb 下载 &#xff0c;选择编译好的二进制文件&#xff0c;libusb-1.0.26-binaries.7z libusb Activity 2. 解压 3. 在 Qt Widgets Application 或者 Qt Console Application 工程中导入库&#xff0c;Qt 使用的是 minggw 64编译器&#xff0c;所以选择libusb-MinGW-x64。…

基于STM32的电子钟与万年历设计

1、功能 硬件部分&#xff1a; (1). 采用 STM32F103ZET6作为主控芯片&#xff0c; 负责驱动其他外设模块 (2). 实时时钟采用 STM32 本身的 RTC (3). TFT(LCD)彩色显示屏 正点原子的3.5寸触摸屏&#xff08;NT3510&#xff09; (4). DS18B20 温度传感器 支持的功能&#xf…