基本GMP模型内容
- 全局G队列:等待运行的G。加入读取都需要加锁。
- 本地G队列:每个P都会有一个。该队列最大存储256个。
- P:可用通过
$GOMAXPROCS
环境变量和runtime.``GOMAXPROCS
设置 - M:最大1000个,理论值实际不可能达到。
调度器的设计
- 复用线程:work stealing, hand off。本地队列 -> 全局队列 -> 偷取一半3
- 利用并行:
$GOMAXPROCS
- 抢占式:每一个G最多运行10ms,会起一个单独的M运行sysmon监控G的执行时间,不是直接抢占,而是对G进行标记(1.14)
- 全局G:
go 指令调度
会优先放入当前G的P的本地队列,满了才会放到全局G队列中
M0
- 编号为0的系统级线程,放到全局变量runtime.m0中,主要负责启动第一个G0
- 启动完第一个G0,M0就和其他的M一样。该G0主要负责初始化创建多个P,然后调度运行main.main
- 在创建完main.main并放入一个P,就销毁了
G0
- 每次启动一个M都会创建一个goroutine,就是G0
- G0负责调度G,G0不会执行任何函数
- 在调度或者系统调用的时候,M会用G0来调度,G0放在一个单独的全局空间
几个关键字
- gomaxprocs:P的数目
- idleprocs:处于刚分配还未初始化的的P (gomaxprocs - idleprocs = 目前在处理的P)
- spinningthreads:自旋线程
- idlethreads:
- runqueue
唤醒 和 自旋
自旋
- 从M休眠队列 唤醒一个g0,g0自旋,然后会从全局拿G,拿的过程会有一个负载均衡。全局没有回去别的P的本地队列偷一半过来。
- 自旋线程 + 执行线程 <= GOMAXPROCS。对于的线程M都会在休眠队列
- 自旋线程是抢占G的
唤醒
- syscall/阻塞:G和M绑定,P去寻找其他休眠的M 或 创建一个新的M 或 进入P的休眠队列。
- 阻塞 -> 非阻塞 / syscall 完成:M 会先去抢原来的P -> 去P空闲队列找 -> 进入M的休眠队列。 G会被放入全局队列中。
M休眠队列长期休眠就会销毁了