在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设计思维
发表回复