其他分享
首页 > 其他分享> > 【简洁版】Go语言GPM模型梳理

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

作者:互联网

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

0. 前提知识

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

a.协程的M:N关系

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

b.Golang中的协程Goroutine

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

1. 相关定义

2. GMP模型

两个队列

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

两个调度器

两个数量

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 调度流程

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变为空闲状态。

标签:GPM,协程,队列,创建,调度,线程,本地,Go,梳理
来源: https://blog.csdn.net/weixin_38371073/article/details/122800875