首页 > 其他分享 >Golang并发

Golang并发

时间:2024-06-21 12:42:51浏览次数:21  
标签:加锁 协程 获取 goroutine 阻塞 Golang 并发 自旋

Sync.Mutex

Mutex结构

type Mutex struct {
	state int32
	sema  uint32
}

Sync.Mutex由两个字段构成,state用来表示当前互斥锁处于的状态,sema用于控制锁状态的信号量

互斥锁state(32bit)主要记录了如下四种状态:

  • waiter_num(29bit):记录了当前等待这个锁的goroutine数量
  • starving(1bit):当前锁是否处于饥饿状态,0: 正常状态 1: 饥饿状态
  • woken(1bit):当前锁是否有goroutine已被唤醒。 0:没有goroutine被唤醒; 1: 有goroutine正在加锁过程
    Woken 状态用于加锁和解锁过程的通信,例如,同一时刻,两个协程一个在加锁,一个在解锁,在加锁的协程可能在自旋过程中,此时把 Woken 标记为 1,用于通知解锁协程不必释放信号量了。
  • locked(1bit):当前锁是否被goroutine持有。 0: 未被持有 1: 已被持有

sema信号量的作用:当持有锁的gorouine释放锁后,会释放sema信号量,这个信号量会唤醒之前抢锁阻塞的gorouine来获取锁。

锁的两种模式

互斥锁主要有两种模式: 正常模式和饥饿模式
之所以引入了饥饿模式,是为了保证goroutine获取互斥锁的公平性。公平性是指多个goroutine在获取锁时,goroutine获取锁的顺序,和请求锁的顺序一致。
正常模式下,所有阻塞在等待队列中的goroutine会按顺序进行锁获取,当唤醒一个等待队列中的goroutine时,此goroutine并不会直接获取到锁,而是会和新请求锁的goroutine竞争。通常新请求锁的goroutine更容易获取锁,这是因为新请求锁的goroutine正在占用cpu片执行,大概率可以直接执行到获取到锁的逻辑。
这样就会出现阻塞队列中的goroutine一直在等待的情况,因此引入了饥饿模式。
饥饿模式下, 新请求锁的goroutine不会进行直接进行锁获取,而是加入到队列尾部阻塞等待获取锁。

饥饿模式的触发条件

  • 当一个goroutine等待锁的时间超过1ms时,互斥锁会切换到饥饿模式
    自旋过程抢到锁,意味着同一时刻有协程释放了锁,释放锁时如果发现有阻塞等待的协程,还会释放一个信号量来唤醒一个等待协程,被唤醒的协程得到 CPU 后开始运行,此时发现锁已被抢占了,自己只好再次阻塞,不过阻塞前会判断自上次阻塞到本次阻塞经过了多长时间,如果超过 1ms 的话,会将 Mutex 标记为”饥饿”模式,然后再阻塞。

饥饿模式的取消条件

  • 当获取到锁的这个goroutine是等待锁队列中的最后一个goroutine,互斥锁会切换到正常模式;
  • 当获取到锁的这个goroutine的等待时间在1ms之内,互斥锁会切换到正常模式

自旋

自旋过程是指,goroutine尝试加锁时,如果当前 Locked 位为 1,则说明该锁当前是由其他协程持有,尝试加锁的协程并不会马上转入阻塞队列,而是会持续的探测 Locked 位是否变为 0。自旋的时间很短,但如果在自旋过程中发现锁已被释放,那么协程可以立即获取锁。此时即便有协程被唤醒也无法获取锁,只能再次阻塞,这个可不是我们想要的。
自旋的好处是,当加锁失败时不必立即转入阻塞,有一定机会获取到锁,这样可以避免协程的切换。但是自旋会导致阻塞队列中的goroutine一直获取不到锁,处于饥饿状态

自旋必须满足以下所有条件:

  • 自旋的次数要足够小,通常为4,即「自旋最多为4次」
  • CPU 核数要大于1,否则自旋是没有意义的,因为此时不可能有其他协程释放锁
  • 协程调度机制中的 Process 数量要大于 1,比如使用 GOMAXPROCS() 将处理器设置为 1 就不能启用自旋
  • 协程调度机制中的可运行队列必须为空,否则会延迟协程调度

为了避免协程长时间无法获取锁,自1.8版本以来增加了一个状态,即 Mutex 的 Starving 状态。这个状态下不会自旋,而是直接进入阻塞队列,一旦有协程释放锁,那么一定会唤醒一个阻塞队列中的协程并成功加锁。

标签:加锁,协程,获取,goroutine,阻塞,Golang,并发,自旋
From: https://www.cnblogs.com/DCFV/p/18260291

相关文章

  • CompletableFuture多线程并发处理
    CompletableFuture多线程并发处理   概要  一个接口可能需要调用N个其他服务的接口,这在项目开发中还是挺常见的。举个例子:用户请求获取订单信息,可能需要调用用户信息、商品详情、物流信息、商品推荐等接口,  如果是串行(按顺序依次执行每个任务)执行的话,接口的响应速......
  • golang interface guard 技术(接口守护)
     Go代码的接口守卫(interfaceguards)技术,通常用于库的开发,以确保类型符合预期的接口。 下面示例的Interfaceguards确保App类型实现了caddy.App、caddy.Provisioner和caddy.Validator接口。具体来说,它通过将(*App)(nil)转换为这三个接口的类型,并将其赋值给匿名变量......
  • MVCC多版本并发控制
    MVCC(MultiVersionConcurrencyControl)多版本并发控制,是指在使用READCOMMITTED、REPEATABLEREAD这两种隔离级别的事务执行SELECT操作时访问记录的版本链的过程,使不同事务的读写操作能够并发执行,提升系统性能。MVCC机制的核心是在做SELECT操作前会生产一个ReadView,READCO......
  • golang 爬虫修炼02 ---协程、互斥锁、读写锁、waitgroup
    协程程序:为了完成特定任务,使用某种语言编写的一组指令的集合,是一段静态的代码进程:是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。进程是动态的,有产生、存在、消亡的过程线程:进程可进一步细分为线程,是一个程序......
  • 深入理解AQS:Java并发编程中的核心组件
    目录AQS简介AQS的设计思路AQS的核心组成部分状态(State)同步队列(SyncQueue)条件队列(ConditionQueue)AQS的内部实现节点(Node)锁的获取与释放独占锁共享锁条件变量AQS的应用案例ReentrantLockCountDownLatchSemaphore总结参考文献AQS简介AbstractQueuedSynchronizer(AQ......
  • Golang - 90天从新手到大师
    开篇最近有很多小伙伴都在寻找go语言完整学习资料,我整理了一些Golang方面的知识,方便大家学习。内容从最基础的入门到项目设计,希望帮助更多想了解和学习Go语言的伙伴。因为是持续创作,所以也会持续更新。有些章节目录还没有内容,敬请期待。。创作不易,感谢大家的支持。如果看后......
  • 【golang学习之旅】Go程序快速开始 & Go程序开发的基本注意事项
    系列文章【golang学习之旅】使用VScode安装配置Go开发环境【golang学习之旅】报错:adeclaredbutnotused【golang学习之旅】Go的基本数据类型【golang学习之旅】深入理解字符串string数据类型【golang学习之旅】gomodtidy【golang学习之旅】记录一次paniccase......
  • 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念
    理解并发概念并发(Concurrent):多个线程在单个核心运行,同一时间只能一个线程运行,内核不停切换线程,看起来像同时运行,实际上是线程被高速的切换.通俗好理解的比喻就是高速单行道,单行道指的是CPU的核数,跑的车就是线程(任务),进程就是管理车的公司,一个公司可以有很多台车.并发......
  • Java项目开发中异步调用场景控制并发数
    场景项目基于SpringBoot搭建,默认使用TomcatWeb容器,对于每个HTTP请求,TomcatWeb容器会分配1个线程来处理请求。在pom.xml里查看依赖关系:spring-boot-starter-web添加了tomcat-embed-core依赖Tomcat线程池配置可在application.yml配置:server:tomcat:max-threads:5......
  • 一张图看懂大模型性价比:能力、价格、并发量全面PK
    最近,国内云厂商的大模型掀起一场降价风暴。火山引擎、阿里云、百度云等纷纷宣布降价,部分模型价格降幅据称高达99%,甚至还有些模型直接免费。五花八门的降价话术,一眼望去遍地黄金。但事实真的如此吗?今天我们就拨开迷雾,深挖下大模型降价背后那些事。1选大模型要最强版还是......