GPM 模型
golang 在系统调度的基础上实现了自己的 goroutine 调度器,即 GPM 模型。goroutine 相比线程更加轻量,GPM 调度器效率更高,因此 go 可以有很强的并发能力
G:goroutine
P:Processor,相当于 G 的CPU,数量等于 GOMAXPROC
M:执行 G 的线程
GPM 简单原理:
每个 P 都有一个局部队列,负责保存待执行的 G,当局部队列满了就放到全局队列中
每个 P 都有一个 M 绑定,正常情况下 M 从局部队列中获取 G 执行
M 可以从其他队列偷取 G 执行(work stealing),也可以从全局队列获取 G 执行
当 G 因系统调用(syscall)阻塞时会阻塞 M,此时 P 会和 M 解绑(hand off),并寻找新的空闲 M,若没有空闲 的M 就会新建一个 M
当 G 因 channel 或者 network I/O 阻塞时,不会阻塞 M,M 会寻找其他 runnable 的 G;当阻塞的 G 恢复后会重新进入 runnable 进入 P 队列等待执行
mcache(内存分配状态)位于 P,所以 G 可以跨M调度,不再存在跨 M 调度局部性差的问题
G 是抢占调度。不像操作系统按时间片调度线程那样,Go 调度器没有时间片概念,G 因阻塞和被抢占而暂停,并且 G只能在函数调用时有可能被抢占,极端情况下如果 G 一直做死循环就会霸占一个 P 和 M,Go 调度器也无能为力。
GODEBUG
使用 GODEBUG 可以查看 Go 调度器的实际过程:
#含义就是每1000ms,打印输出一次goroutine scheduler的状态
GODEBUG=schedtrace=1000 ./goutils
#输出
gomaxprocs:P 的数量
idleprocs:空闲的 P
threads:总线程数
spinningthreads:自旋线程数
idlethreads:空闲线程数
runqueue:全局队列中 G 的数目
[2 3 3 2 1 1 2 1]:局部队列中 G 的数目