大家好,我是煎鱼。
关于 Go 的 GMP 调度模型,我们之前写过一篇《Go 群友提问:Goroutine 数量控制在多少合适,会影响 GC 和调度?》进行深入讲解和分析。
当时有同学提出了如下问题:
![4ed9d00b5f5116c5b6ef66258f3cb3a1.png](https://img-blog.csdnimg.cn/img_convert/4ed9d00b5f5116c5b6ef66258f3cb3a1.png)
GPM 模型中,M 的实际数量,多或少受什么因素的限制呢?
作为一只鸽鱼,今天就给大家来讲讲,明确这一个小细节点。
回顾
之前有提到 Go 的调度模型离不开一个家庭里三个兄弟的互相协作,我们在此进行简单的回顾。
![e34a8823705c7fd053caee063265bf82.png](https://img-blog.csdnimg.cn/img_convert/e34a8823705c7fd053caee063265bf82.png)
这三位大哥分别是:G 弟、P 哥、M 爸,其分别承担如下作用:
G:Goroutine,实际上我们每次调用 go func 就是生成了一个 G。
P:Processor,处理器,一般 P 的数量就是处理器的核数,可以通过 GOMAXPROCS 进行修改。
M:Machine,系统线程。
三者的协作方式是:M: N 调度模型。M 必须与 P 进行绑定,然后不断地在 M 上循环寻找可运行的 G 来执行相应的任务。
前文已经把 G 和 P 的限制的因素说的很清楚了,如下图:
![04af91af62a74e3995ddb0a282d8d3c8.png](https://img-blog.csdnimg.cn/img_convert/04af91af62a74e3995ddb0a282d8d3c8.png)
下面会注重 M 的描述。
M 受影响因素
默认约束
M(Machine)是系统线程,在 Go 中默认的数量限制是 10000,如果超出则会报错:
GO: runtime: program exceeds 10000-thread limit
可以通过 debug.SetMaxThreads
的方法进行调整上限数量。
实际数量
那 M 的实际数量和什么有关系呢?本质上与 M 是否空闲和是否忙碌有关。
如果在调度时,发现没有足够的 M 来绑定 P,P 中又有需要就绪的任务,就会创建新的 M 来绑定。
如果有空闲的 M,自然也就不会创建全新的 M 了,会优先使用。
源代码
对应创建 M 的代码是 startm 函数。
源代码(src/runtime/proc.go)如下:
func startm(_p_ *p, spinning bool) {
...
nmp := mget()
if nmp == nil {
id := mReserveID()
unlock(&sched.lock)
var fn func()
if spinning {
fn = mspinning
}
newm(fn, _p_, id)
releasem(mp)
return
}
...
}
通过 mget
函数获取是否有空闲的 M,若无,则会调用 newm
函数创建一个新的 M 来应用,逻辑不会太复杂。
可能会有同学想问,那在什么场景下会涉及呢?理论上只要是 Go 调度器运行的过程中,发现满足条件都有可能会创建。
没必要太死记硬背,因为这个场景是会不断增加的,记住 GMP 模型的基本原则和三者的影响因素就可以了。
总结
这篇文章主要是针对前文的补充,增强了在 GMP 模型中 M 的实际数量的影响因素和源代码的逻辑处的介绍。
作为抛砖引玉,希望对大家在 Go 的调度模型上有一些帮助。
关注煎鱼,获取业内第一手消息和知识 👇
你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路。
日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!