在 Go 中,线程是运行 goroutine
的实体,调度器的性能是把可运行的 goroutine
调配到工作线程上。
- 全局队列(
Global Queue
):寄存期待运行的 G。 - P 的本地队列:同全局队列相似,寄存的也是期待运行的 G,存的数量无限,不超过 256 个。新建 G ’ 时,G’ 优先退出到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 挪动到全局队列。
- P 列表:所有的 P 都在程序启动时创立,并保留在数组中,最多有
GOMAXPROCS
(可配置) 个。 - M:线程想运行工作就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其余 P 的本地队列偷一半放到本人 P 的本地队列。
M
运行G
,G 执行之后,M 会从 P 获取下一个 G,一直反复上来。Goroutine
调度器和OS
调度器是通过M
联合起来的,每个M
都代表了1
个内核线程,OS 调度器负责把内核线程调配到CPU
的核上执行。
无关 P 和 M 的个数问题
1、P 的数量
由启动时环境变量 $GOMAXPROCS
或者是由 runtime
的办法 GOMAXPROCS()
决定。这意味着在程序执行的任意时刻都只有 $GOMAXPROCS
个goroutine
在同时运行。
2、M 的数量
go 语言自身的限度:go 程序启动时,会设置M
的最大数量,默认 10000
. 然而内核很难反对这么多的线程数,所以这个限度能够疏忽。runtime/debug
中的 SetMaxThreads
函数,设置 M 的最大数量
一个 M
阻塞了,会创立新的 M。
M 与 P 的数量没有相对关系,一个 M
阻塞,P
就会去创立或者切换另一个 M
,所以,即便 P 的默认数量是1
,也有可能会创立很多个M
进去。
3、P 和 M 何时会被创立
P
何时创立:在确定了 P 的最大数量n
后,运行时零碎会依据这个数量创立n
个P
。M
何时创立:没有足够的 M 来关联P
并运行其中的可运行的G
。比方所有的 M 此时都阻塞住了,而 P 中还有很多就绪工作,就会去寻找闲暇的 M,而没有闲暇的,就会去创立新的 M。
参考资料
Golang 的协程调度器原理及 GMP 设计思维