【简洁版】Go语言GPM模型梳理

参考来源:30+张图讲解:Golang调度器GMP原理与调度全分析 (qq.com)

0. 前提知识

对比分析进程、线程与协程 (htmonster.xyz)

a.协程的M:N关系

为什么是M:N,而不是1:1或者N:1?

  • N:1关系(一个内核线程thread 管理着多个用户协程co-routine
    • 优点:切换开销
    • 缺点:一个挂了就全挂了,一锅端
  • 1:1 关系 (一个内核线程thread 管理着一个用户协程co-routine)
    • 缺点:创建、删除、切换开销

b.Golang中的协程Goroutine

Goroutine其实就是Go语言中的协程。但是不同的是,Golang在语言层次,从runtime系统调用等方面对协程进行了封装。

1. 相关定义

  • G (Goroutine) 协程: 发布出去的任务,由go关键字创建 【携带任务】
  • **P **(Processor) 处理器:调度G到M上,包含包含了gorountine的资源以及可运行的G的队列【分配任务】
  • M (Machine thread) 内核线程:G要放在M上才能运行【寻找执行任务】

2. GMP模型

两个队列

  • 全局队列(Global Queue):存放等待运行的G
  • **P的本地队列:**也是存放等待运行的G,但是数量有限(256)。

新创建的G’, 优先加入P的本地队列。若队列满了,会将本地队列中的一半移入到全局队列。

两个调度器

  • Goroutine调度器:将G分配给M
  • OS调度器: 将M分配到CPU上

两个数量

  • P列表:程序启动时候创建,由a. 环境变量$GOMAXPROCS或者是由b.runtime的方法GOMAXPROCS()决定
  • M列表: 程序运行时没有空闲M时候创建。由a. go程序启动时最大数量默认10000 b. runtime/debug中的SetMaxThreads函数设置 c. M阻塞了,会创建新的M决定。

3. Goroutine调度

Go调度本质是把大量的goroutine分配到少量线程上去执行,并利用多核并行,实现更强大的并发

2.1设计策略

a. 复用线程

避免频繁的创建、销毁线程,而是对线程的复用

  1. work stealing机制: 线程中没有G时候,从别的绑定P中偷取G
  2. hand off机制: 本线程阻塞时候,释放P给别的线程
b. 并行利用

最多有GOMAXPROCS个线程分布在多个CPU上同时运行

c. 抢占

在Go中,一个goroutine最多占用CPU 10ms

d. 全局队列

当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G。

2.2 go func() 调度过程

  1. go func() 创建一个G
  2. G加入到队列中
    1. 先尝试加入局部队列
    2. 局部队列满了会尝试加入全局队列
  3. P从队列中获取GM与G是1:1的关系
    1. M会从本地队列中取出一个可执行的G
    2. 入股本地队列为空,会从其它的MP组合中偷取一个可执行的G
  4. M循环调度G
  5. M执行G.func()
    1. G如果发生了syscall或则其余阻塞操作,M会阻塞,runtime把线程M从P中摘除(detach)
    2. 创建一个新M或者复用一个休眠M
    3. M来服务于这个P
  6. 销毁G
  7. 返回

2.3 go func 调度流程

  • M0: 启动后的主线,M0负责执行初始化操作启动第一个G, 在之后M0就和其他的M一样了。
  • G0:每次启动一个M都会第一个创建的gourtine,G0仅用于负责调度的G,G0不指向任何可执行的函数, 每个M都会有一个自己的G0

4. Go调度器执行场景

4.1 G1创建G2

P拥有G1,M1获得P,M开始运行G1,G1中创建了G2,G2优先加入了P1的本地队列。

4.2 G1执行完毕

G0执行完毕退出,切换到G0,G0调度G2到M1中

4.3 G2开辟过多的G

G2创建过多的G导致P1的本地队列

4.4 P1本地队列负载均衡

当新创建的G无法再加入本地队列时候,将新G和本地队列中一半的G移入全局队列。

4.5 G2本地队列未满加入G8

创建G8的时候,本地队列P1还没满,则加入到P1的本地队列中。

4.6 唤醒休眠M

当创建G的时候,会首先尝试唤醒其它空闲的P-M组合去执行G

4.7 M2尝试批量获取G

自旋线程M2会尝试从全局队列中获得一批G到P2本地队列中

4.8 M2偷取P1本地队列中的G

当全局队列和P2本地队列中没有G的时候,会尝试去P1本地队列中偷取G

4.9 关闭多余的M

当许多的M在自旋(运行),为了不浪费CPU资源,会限制最大数量的自旋线程。

4.10 G阻塞时候P切换M绑定

当G阻塞时候,M2与P2立即解绑。如果还有G或者还有空闲M,会唤醒一个M与其绑定,否则则会加入空闲P队列。

4.11 G非阻塞时候P-M切换绑定

当G非阻塞系统调用时候,同场景10。但是不同的是,当系统调用返回时候,会尝试恢复绑定原来的P。当失败时候则G加入全局,M变为空闲状态。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值