中文名 源码名称 作用域 简要说明
全局M列表 runtime.allm 运行时系统 存放所有M
全局P列表 runtime.allp 运行时系统 存放所有P
全局G列表 runtime.allg 运行时系统 存放所有G
调度器中的空闲M列表 runtime.schedt.midle 调度器 存放空闲M,链表结构
调度器中的空闲P列表 runtime.schedt.pidle 调度器 存放空闲P,链表结构
调度器中的可运行G队列 runtime.schedt.runq 调度器 存放可运行G,链表结构
调度器中的自由G列表 runtime.schedt.gfree 调度器 存放自由G, 链表结构
P中的可运行G队列 runq 本地P 存放当前P中的可运行G,环形队列,数组实现
P中的自由G列表 gfree 本地P 存放当前P中的自由G,链表结构
三个全局的列表主要为了统计runtime的所有G、M、P。我们主要关心剩下的这些容器,尤其是和G相关的四个。
在runtime创建的G都会被保存在全局的G列表中,值得注意的是:
从Gsyscall转出来的G,如果不能马上获取空闲的P执行,就会被放置到全局调度器的可运行队列中(global queue)。
被runtime初始化的G会被放置到本地P的可运行队列中(local queue)
从Gwaiting转出来的G,除了因网络IO陷入等待的G之外,都会被防止到本地P可运行的G队列中。
转成Gdead状态的G会先被放置在本地P的自由G列表。
调度器中的与G、M、P相关的列表其实只是起了一个暂存的作用。
M是golang对内核OS线程的更上一层抽象,所以M也没有专门字段来维护状态,简单来说有一下几种状态:
自旋中(spinning): M正在从运行队列获取G, 这时候M会拥有一个P;
执行go代码中: M正在执行go代码, 这时候M会拥有一个P;
执行原生代码中: M正在执行原生代码或者阻塞的syscall, 这时M并不拥有P;
休眠中: M发现无待运行的G时会进入休眠,并添加到空闲M链表中, 这时M并不拥有P。
P是有状态机的(五种):
Pidel:当前P未和任何M关联
Prunning:当前P已经和某个M关联,M在执行某个G
Psyscall:当前P中的被运行的那个G正在进行系统调用
Pgcstop:runtime正在进行GC(runtime会在gc时试图把全局P列表中的P都处于此种状态)
Pdead:当前P已经不再被使用(在调用runtime.GOMAXPROCS减少P的数量时,多余的P就处于此状态)
大致上和内核线程的状态机有一点类似,但是状态机流转有一些区别。G的各种状态如下:
Gidle:G被创建但还未完全被初始化。
Grunnable:当前G为可运行的,正在等待被运行。
Grunning:当前G正在被运行。
Gsyscall:当前G正在被系统调用
Gwaiting:当前G正在因某个原因而等待
Gdead:当前G完成了运行
https://blog.csdn.net/u010853261/article/details/84790392
https://blog.csdn.net/u010853261/article/details/84901386
https://xargin.com/go-scheduler/
http://www.imooc.com/article/292206
https://blog.csdn.net/qq_25504271/article/details/81000217
https://github.com/fuweid/notes/blob/master/golang/goroutine_scheduler_overview.md
https://www.cnblogs.com/YDDMAX/p/4979878.html