Go GMP了解

Go语言的并发模型基于GMP(Goroutine、Mutex、Processor),通过引入Processor(P)缓解了线程创建和调度的开销。每个P都有本地G队列,当队列满时,会将一半G移至全局队列。线程M从P队列获取G执行,堵塞时会释放P,其他M可接管。G0是每个M的调度辅助goroutine,不执行具体任务。预占策略包括信号中断和周期性检查。Go的并发设计有效平衡了性能和资源消耗。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

G: goroutine

M: thread 线程

P: Processor 包含运行goroutine的资源

GM

在这里插入图片描述

在GM模型中,M想要执行、返回G都必须访问全局G队列。这会导致以下缺点

  • 创建、销毁、调度G都需要每个M获取锁,会导致激烈的锁竞争
  • M转移G会造成延迟和额外的系统负担。比如当G1包含创建新goroutine G2时,M1为了继续执行G1,就将G2交给M2执行,造成了很差的局部性。因为G1、G2是相关的
  • 系统调用导致频繁的线程堵塞和取消堵塞操作增加了系统开销

GMP

为了应对上述问题,又引入了P(Processor),包含运行goroutine的资源以及可运行的G队列。如果线程想运行goroutine,必须先获取P

在这里插入图片描述

  • 全局队列:存放等待运行的G
  • P的本地队列:存放的也是等待运行的 G。存的数量有限,不超过 256 个。P中GxG_xGx新建$ G_y时,时,时, G_y$优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。
  • P列表:所有的P都在程序启动时创建,并保存在数组中。最多有GOMAXPROCS个
  • M:线程M需要通过P执行任务。首先尝试从P的本地队列获取G,若本地P空,则从全局队列获取一批G到本地P队列,在全局队列空的情况下,还可以从其他P队列偷一半到本地P

P、M相关问题

数量

p数量:由GOMAXPROCS决定

m数量:go设置的默认最大数量为10000,runtime/debug中SetMaxThreads也可以设置m的最大数量

m、p数量没有绝对关系,一个m堵塞p就会去创建或切换另一个m

创建时间

p:运行时会根据最大数量创建

m:不够就创建

调度流程

  1. 创建goroutine G1
  2. 将G1保存到本地P队列。若本地P队列满,则放(前一半G和新创建)至全局G队列
  3. M从本地P队列弹出可执行状态的G执行。若P队列为空,则会从全局G队列取,若全局队列空,就会从其他MP组合偷取一半可执行的G
  4. 当M执行某个G时,突然发生syscall或者堵塞操作,M就会发生堵塞,将M从P中detach,然后复用或者创建新的线程来服务该P
  5. 如果M没有goroutine可执行,就会一直处于自旋找寻goroutine(最多GOMAXPROCS自旋线程,多余休眠);P没有M绑定,则加入空闲P列表,等待M唤醒(M会优先绑定之前绑定的P)。
    在这里插入图片描述

M0、G0

M0是启动程序后编号为0的主线程,这个M对应实例在全局变量runtime.m0,不住堆分配。M0负责初始化操作和启动第一个G,之后M0便泯然众M矣

G0是每次启动一个 M 都会第一个创建的 goroutine,G0 仅用于负责调度的 G,G0 不指向任何可执行的函数,每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0 的栈空间,全局变量的 G0 是 M0 的 G0。

堵塞

用户态堵塞

当goroutine因为goroutine操作或者network IO(golang已经用netpoller实现了goroutine网络I/O阻塞不会导致M被阻塞,仅阻塞G)堵塞时,对应的G会放置到某个wait队列(如channel的waitq),该G状态由_Grunning变为_Gwaiting;而M会跳过该G尝试执行下一个G

内核态堵塞

堵塞在系统调用时,G处于_Gsyscall状态,M也处在block on syscall状态,此时M可悲抢占调度;执行该G的M会与P解绑,P会尝试与其他idle M绑定

抢占

为了不使goruntine长期霸占运行资源,需要有抢占。

  • signals:通过外界信号中断原来的线程执行
  • cooperative checks:通过线程间歇性轮询check自己的运行时间来主动暂停

对于go来说cooperative checks更为合理,平台无关、非抢占式,并且代码编译也是golang编译器控制的

死循环就无法check啦

系统调用堵塞时,让出P执行权,交接给idle M;执行结束后,进行idle状态等待唤醒

反思

  • 线程池:既能拥有多线程提供的强大的并发能力,同时避免了线程过多带来的过大开销
  • 资源池:线程池就是一种资源池。资源池可以对一定规模约束的资源进行池化管理
  • 计算储存分离:相比于GM模型,新来的P就是将G队列和相关储存资源移到了P,那么当M堵塞时,与M绑定的P便可以去找别的M利用P中已有的储存资源以及G

Ref

  1. https://learnku.com/articles/41728
  2. https://go.cyub.vip/gmp/gmp-model.html
  3. https://mp.weixin.qq.com/s/N1ColaDQtm7-LM5IqrJWqw
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值