Go-深入理解GMP
深入理解GMP
为什么需要调度器
- 线程/进程数量越多,切换成本越大
- 多线程 随着 同步竞争(如 锁。资源)
- 进程和线程内存占用大
什么是GMP
G:goroutine协程
P:processor处理器
M: thread内核级线程(machine)
调度器的设计策略
复用线程:避免频繁的创建、销毁线程,而是对线程的复用
1)work stealing机制
当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程
先从全局队列拿,全局没有的时候,在从其他线程绑定的P的本地队列“窃取”
执行条件
1、当前p本地队列有待执行g
2、没有空闲的p和m, 全局g队列为空 (此时意味这全局繁忙)
3、 需要处理网络 I/O
2)hand off机制
当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行
3)go func()调度过程
- go func()创建一个goroutine
- 有俩个存储G的本地,新建G优先放在P的本地队列,本地队列满就放在全局队列
- G指南运行在M,一个M必须有一个P,M与P是1:1的关系。P的本地为空,M会从(先全局后其他P的本地队列)拿一个G
- 一个M一个G的不断循环的过程
- 当M执行G的时候发生syscall或者阻塞,M会阻塞,当前一些G在执行,runtime就会吧M从P的删除,创建一个新的M去绑定P(P舍弃M,绑定新的M)
- M调用结束,拿一个新的G,拿不到,M进入休眠
GO的启动周期
M0和G0
M0:
- 启动程序后的编号为0的主线程
- 在全局变量runtime.m0中,不需要在heap上分配
- 负责执行初始化操作和启动第一个G
- 启动第一个G之后,M0和其他M同等
G0:
- 每次启动第一个M,都会第一个创建的gourtime,就是G0
- G0仅用于负责调度G
- G0不指向任何可执行函数G0就是来调度其他G,所有G得指挥所
- 每一个M都有一个自己的G0
- 在调度或者系统调用时候会使用M来切换到G0
- M0的G0在全局空间中
GMP可视化
trace调试
基本的trace:
- 创建trace文件 f, err:=os.Create(“trace.out”)
- 启动trace trace.Start()
- 停止trace trace.Stop()
- Go build 并且运行之后,会得到一个trace.out文件
- 通过go tool trace 工具打开trace文件,
go tool trace trace.out
GMP终端GODEBUG调试
- gomaxprocess P的数量 一般默认是和CPU核心数是一样的
- idlleprocs 处理idle状态的P数量,gomaxprocs-idlleprocs= 目前正在执行的p的数量
- threads 线程数量
- spinningthreads 处于自旋的thread数量
- idllethread 处理idle状态的thread数量
- runqueue 全局G队列中的G的数量
- 【0,0】每个P的local queue本地队列,目前存在G的数量
# 先go build xx.go 生成文件 |
GMP场景
场景1:G1创建G3
局部性:G3优先加入G1所在M(他们之间可能共享资源)【创建G优先在G所在M】
场景2:G1执行完毕
G1在完成退出之后,拿到G0,G0优先从本地队列P中取到其他G 运行
场景3、4、5:G2开辟过多G,全局队列满,全局队列不满
G2想要创建的G 超过了本地队列P的最大容量
G7,G8就是还要多出来创建的G
- 先存满本地队列P,满了把G7放入全局队列
- 平均分割本地队列P的一半
- 把前面一半G放入全局队列,同时如果本地队列满,不满创建G8
场景6:唤醒正在休眠的M
每次创建G,就会尝试唤醒M(前提有休眠队列中有空闲M)
- M寻找P绑定
- 本地队列P如果是空的,进入自旋寻找G
场景7:被唤醒的M从全局队列获取G
实现了全局队列到P的负载均衡
因为唤醒的M,此时本队队列P为空
n=min(len(GQ全局队列长度)/GOMAXPROCS+1,len(GQ)/2)
- 此时唤醒的M的本队P尝试从全局队列中获取n个G
场景8:M2从M1中偷取G
当M2中的本地队列P为空的时候,并且全局队列也为空
此时,分割一半M1的本地队列P,
M2的本地队列P”偷取”后一半的G到本地队列P
场景9:自旋线程的最大限制
GOMAXPROCS是P的数量限制
自旋线程+执行线程<=GOMAXPROCS
场景10:G发生系统调用/阻塞
- 阻塞|系统调用的G,直接绑定到M上
- 此时阻塞的M的P去寻找新的P,不然只能P进入到空闲队列P
- 找到新的M之后,P绑定到新的M5上
场景11:G发生非阻塞
G8恢复到非阻塞的状态,此时缺少P
- G8先寻找原配P,P2已绑定,就寻找其他空闲P
- G8找不到空闲P,就G8进入全局队列,M2进入休眠线程队列
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Joohwan!
评论