单机的 Goroutine 数量控制在多少比较合适?
发布日期:2022-02-01 16:54:03 浏览次数:36 分类:技术文章

本文共 3566 字,大约阅读时间需要 11 分钟。

大家好,我是煎鱼。

前几天在读者交流群里看到一位小伙伴,发出了一个致命提问,那就是:“单机的 goroutine 数量控制在多少比较合适?”。

也许你和群内小伙伴第一反应一样,会答复 “控制多少,我觉得没有定论”。

紧接着延伸出了更进一步的疑惑:“goroutine 太多了会影响 gc 和调度吧,主要是怎么预算这个数是合理的呢?

这是本文要进行探讨的主体,因此本文的结构会是先探索基础知识,再一步步揭开,深入理解这个问题。

Goroutine 是什么

Go 语言作为一个新生编程语言,其令人喜爱的特性之一就是 goroutine。Goroutine 是一个由 Go 运行时管理的轻量级线程,一般称其为 “协程”。

go f(x, y, z)

操作系统本身是无法明确感知到 Goroutine 的存在的,Goroutine 的操作和切换归属于 “用户态” 中。

Goroutine 由特定的调度模式来控制,以 “多路复用” 的形式运行在操作系统为 Go 程序分配的几个系统线程上。

同时创建 Goroutine 的开销很小,初始只需要 2-4k 的栈空间。Goroutine 本身会根据实际使用情况进行自伸缩,非常轻量。

func say(s string) { for i := 0; i < 9999999; i++ {  time.Sleep(100 * time.Millisecond)  fmt.Println(s) }}func main() { go say("煎鱼") say("你好")}

人称可以开几百几千万个的协程小霸王,是 Go 语言的得意之作之一。

调度是什么

既然有了用户态的代表 Goroutine,操作系统又看不到他。必然需要有某个东西去管理他,才能更好的运作起来。

这指的就是 Go 语言中的调度,最常见、面试最爱问的 GMP 模型。因此接下来将会给大家介绍一下 Go 调度的基础知识和流程。

下述内容摘自煎鱼和 p 神写的中的章节内容。

调度基础知识

Go scheduler 的主要功能是针对在处理器上运行的 OS 线程分发可运行的 Goroutine,而我们一提到调度器,就离不开三个经常被提到的缩写,分别是:

  • G:Goroutine,实际上我们每次调用 go func 就是生成了一个 G。

  • P:Processor,处理器,一般 P 的数量就是处理器的核数,可以通过 GOMAXPROCS 进行修改。

  • M:Machine,系统线程。

这三者交互实际来源于 Go 的 M: N 调度模型。也就是 M 必须与 P 进行绑定,然后不断地在 M 上循环寻找可运行的 G 来执行相应的任务。

调度流程

我们以 GMP 模型的工作流程图进行简单分析,官方图如下:

0a28f2fe983ead6b9ad07906f87fbeb2.png
  1. 当我们执行 go func() 时,实际上就是创建一个全新的 Goroutine,我们称它为 G。

  2. 新创建的 G 会被放入 P 的本地队列(Local Queue)或全局队列(Global Queue)中,准备下一步的动作。需要注意的一点,这里的 P 指的是创建 G 的 P。

  3. 唤醒或创建 M 以便执行 G。

  4. 不断地进行事件循环

  5. 寻找在可用状态下的 G 进行执行任务

  6. 清除后,重新进入事件循环

在描述中有提到全局和本地这两类队列,其实在功能上来讲都是用于存放正在等待运行的 G,但是不同点在于,本地队列有数量限制,不允许超过 256 个。

并且在新建 G 时,会优先选择 P 的本地队列,如果本地队列满了,则将 P 的本地队列的一半的 G 移动到全局队列。

这可以理解为调度资源的共享和再平衡。

窃取行为

我们可以看到图上有 steal 行为,这是用来做什么的呢,我们都知道当你创建新的 G 或者 G 变成可运行状态时,它会被推送加入到当前 P 的本地队列中。

其实当 P 执行 G 完毕后,它也会 “干活”,它会将其从本地队列中弹出 G,同时会检查当前本地队列是否为空,如果为空会随机的从其他 P 的本地队列中尝试窃取一半可运行的 G 到自己的名下。

官方图如下:

bafa779694573395fbe2eb692c7b4405.png

在这个例子中,P2 在本地队列中找不到可以运行的 G,它会执行 work-stealing 调度算法,随机选择其它的处理器 P1,并从 P1 的本地队列中窃取了三个 G 到它自己的本地队列中去。

至此,P1、P2 都拥有了可运行的 G,P1 多余的 G 也不会被浪费,调度资源将会更加平均的在多个处理器中流转。

有没有什么限制

在前面的内容中,我们针对 Go 的调度模型和 Goroutine 做了一个基本介绍和分享。

接下来我们回到主题,思考 “goroutine 太多了,会不会有什么影响”。

在了解 GMP 的基础知识后,我们要知道在协程的运行过程中,真正干活的 GPM 又分别被什么约束

煎鱼带大家分别从 GMP 来逐步分析。

M 的限制

第一,要知道在协程的执行中,真正干活的是 GPM 中的哪一个

那势必是 M(系统线程) 了,因为 G 是用户态上的东西,最终执行都是得映射,对应到 M 这一个系统线程上去运行。

那么 M 有没有限制呢?

答案是:有的。在 Go 语言中,M 的默认数量限制是 10000,如果超出则会报错:

GO: runtime: program exceeds 10000-thread limit

通常只有在 Goroutine 出现阻塞操作的情况下,才会遇到这种情况。这可能也预示着你的程序有问题。

若确切是需要那么多,还可以通过 debug.SetMaxThreads 方法进行设置。

G 的限制

第二,那 G 呢,Goroutine 的创建数量是否有限制?

答案是:没有。但理论上会受内存的影响,假设一个 Goroutine 创建需要 4k(via @GoWKH):

  • 4k * 80,000 = 320,000k ≈ 0.3G内存

  • 4k * 1,000,000 = 4,000,000k ≈ 4G内存

以此就可以相对计算出来一台单机在通俗情况下,所能够创建 Goroutine 的大概数量级别。

注:Goroutine 创建所需申请的 2-4k 是需要连续的内存块。

P 的限制

第三,那 P 呢,P 的数量是否有限制,受什么影响?

答案是:有限制。P 的数量受环境变量 GOMAXPROCS 的直接影响

环境变量 GOMAXPROCS 又是什么?在 Go 语言中,通过设置 GOMAXPROCS,用户可以调整调度中 P(Processor)的数量。

另一个重点在于,与 P 相关联的的 M(系统线程),是需要绑定 P 才能进行具体的任务执行的,因此 P 的多少会影响到 Go 程序的运行表现。

P 的数量基本是受本机的核数影响,没必要太过度纠结他。

那 P 的数量是否会影响 Goroutine 的数量创建呢?

答案是:不影响。且 Goroutine 多了少了,P 也该干嘛干嘛,不会带来灾难性问题。

何为之合理

在介绍完 GMP 各自的限制后,我们回到一个重点,就是 “Goroutine 数量怎么预算,才叫合理?”。

“合理” 这个词,是需要看具体场景来定义的,可结合上述对 GPM 的学习和了解。得出:

  • M:有限制,默认数量限制是 10000,可调整。

  • G:没限制,但受内存影响。

  • P:受本机的核数影响,可大可小,不影响 G 的数量创建。

Goroutine 数量在 MG 的可控限额以下,多个把个、几十个,少几个其实没有什么影响,就可以称其为 “合理”。

真实情况

在真实的应用场景中,没法如此简单的定义。如果你 Goroutine:

  • 在频繁请求 HTTP,MySQL,打开文件等,那假设短时间内有几十万个协程在跑,那肯定就不大合理了(可能会导致  too many files open)。

  • 常见的 Goroutine 泄露所导致的 CPU、Memory 上涨等,还是得看你的 Goroutine 里具体在跑什么东西。

还是得看 Goroutine 里面跑的是什么东西。

总结

在这篇文章中,分别介绍了 Goroutine、GMP、调度模型的基本知识,针对如下问题进行了展开:

  • 单机的 goroutine 数量控制在多少比较合适?

  • goroutine 太多了会影响 gc 和调度吧,主要是怎么预算这个数是合理的呢?

单机的 goroutine 数量只要控制在限额以下的,都可以认为是 “合理”。

真实场景得看具体里面跑的是什么,跑的如果是 “资源怪兽”,只运行几个 Goroutine 都可以跑死。

因此想定义 “预算”,就得看跑的什么了。


关注煎鱼公众号,吸取精华:

👆 点击关注煎鱼,在知识的海洋里遨游

转载地址:https://blog.csdn.net/slphahaha/article/details/121299321 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:应该如何去选择 Go router?
下一篇:Go 程序错误处理的一些建议

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年03月06日 12时47分44秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

java自带工具_深入了解Java JDK自带工具,包括javac、jar、jstack等,实用~ 2019-04-21
gnome mysql client_解决MySQLWorkbenchgnome-keyring-daemon错误的方法分享 2019-04-21
java线程占用CPU_在windows下揪出java程序占用cpu很高的线程并完美解决 2019-04-21
java多态替换switch_使多态性无法解决那些switch / case语句的麻烦 2019-04-21
java httpclient 进度条_如何使用Apache HttpClient 4获取文件上传的进度条? 2019-04-21
下列不属于java语言特点的是_下列选项中,不属于Java语言特点的一项是( )。... 2019-04-21
java中小数的乘法_javascript的小数点乘法除法实例 2019-04-21
kappa一致性检验教程_SPSS在线_SPSSAU_Kappa一致性检验 2019-04-21
linux shell mysql备份_linux shell 备份mysql 数据库 2019-04-21
Java双向链表时间复杂度_链表是什么?有多少种链表?时间复杂度是? 2019-04-21
unity3d能和java系统整合吗_Android与Unity3d的整合 2019-04-21
minecraft666java_我的世界的666的世界 2019-04-21
辽宁师范大学java_辽宁师范大学心理学院 2019-04-21
java程序有连接数据库_Java程序连接数据库 2019-04-21
java reduce.mdn_reduce高级用法 2019-04-21
java shape用法_Java PShape.scale方法代码示例 2019-04-21
java字符串三目_java字符串连接运算符和三目运算符 2019-04-21
java 堆内存 非堆内存_JVM 堆内存和非堆内存 2019-04-21
Java新手写什么demo_通过入门demo简单了解netty使用方法 2019-04-21
java国际化bundle_java语言国际化--ResouceBundle、struts 2019-04-21