
我们先理解什么是G、M、P
G∶goroutine,一个计算任务。由需要执行的代码和其上下文组成,上下文包括∶当前代码位置,栈顶、栈底地址,状态等。
M∶ machine,系统线程,执行实体,想要在CPU上执行代码,必须有线程,与C语言中的线程相同,通过系统调用 clone 来创建。
P:processor,虚拟处理器,M必须获得P才能执行代码,否则必须陷入休
眠(后台监控线程除外),你也可以将其理解为一种 token,有这个 token,才有在物理 CPU核心上执行的权力。
Go的调度流程本质上是一个生产-消费流程

如上图所示,当我写一个go func的时候就好像我们在往队列里添加了一条数据

P会有一个三级队列
第一级是runext
第二级是local run queue
第三级是global run queue(全局队列)
这些队列就是用来存储我们写一次go func的时候就会生成一个G
那这些G的生成顺序和消费顺序是怎么样的?
一个新的G进来,它是如何存放的呢?
G是如何被消费的呢?
队列的存储与消费是是有优先级的
runnext > local run queue > global run queue
runnext最大长度为1,也就是说runext最多同时挂载一个G
local run queue最大长度为255

上述代码go func表示会生成一个G,这个G是通过runtime.newproc[1]生成的

上述表示go程序执行runtime.runqput函数把G放进runnext中


上述代码go func表示会生成一个G,这个G是通过runtime.newproc[1]生成的

注意上述图一和图二的G
当前正在runnext运行的G会被新生成的G抢占,并被放进local run queue中

因为是local run queue是有最大长度的,因此当local run queue满的时候

Go会把local run queue的G进行打包成一个batch(batch=len(local run queue)/2 +1)
并挂载到global run queue当中去


以上就是Go中G的生成全流程
那么接下来,我们再看看Go中是如何调度和消费G的
我们先来认识一下Go中G的消费流程图

P.schedtick是一个魔法数字,每当schedtick%61=0的时候,它会从全局global run queue中获取G消费

图中红色的G是即将要被消费的G
Go会通过runtime.runqget获取G
然后执行下面👇🏻的流程


图中红色的G是即将要被消费的G
Go会通过runtime.runqget获取G
然后执行下面👇🏻的流程


图中红色的G是即将要被消费的G
Go会通过runtime.runqget获取G
然后执行下面👇🏻的流程


上述图中获取3个G

蓝色G是要即将要被消费的G

剩余两个G会被放到local run queue中


注意上述两幅图中,箭头所指向的P
本文完~