• 体积轻量
    • 优质的GMP调度

    那么goroutine是否可以无限开辟呢,如果做一个服务器或者一些高业务的场景,能否随意的开辟goroutine并且放养不管呢?让他们自生自灭,毕竟有强大的GC和优质的调度算法支撑?

    那么我可以先看如下一个问题。

    结果

    最后被操作系统以kill信号,强制终结该进程。

    所以,我们迅速的开辟goroutine(不控制并发的 goroutine 数量 )会在短时间内占据操作系统的资源(CPU、内存、文件描述符等)。

    • CPU 使用率浮动上涨
    • Memory 占用不断上涨。
    • 主进程崩溃(被杀掉了)

    这些资源实际上是所有用户态程序共享的资源,所以大批的goroutine最终引发的灾难不仅仅是自身,还会关联其他运行的程序。

    所以在编写逻辑业务的时候,限制goroutine是我们必须要重视的问题。


    二、一些简单方法控制goroutines数量

    方法一:只是用有buffer的channel来限制

    1. package main
    2. import (
    3. "fmt"
    4. "math"
    5. "runtime"
    6. )
    7. func busi(ch chan bool, i int) {
    8. fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
    9. <-ch
    10. }
    11. func main() {
    12. //模拟用户需求业务的数量
    13. task_cnt := math.MaxInt64
    14. //task_cnt := 10
    15. ch := make(chan bool, 3)
    16. for i := 0; i < task_cnt; i++ {
    17. ch <- true
    18. go busi(ch, i)
    19. }
    20. }
    1. ...
    2. go func 352277 goroutine count = 4
    3. go func 352278 goroutine count = 4
    4. go func 352279 goroutine count = 4
    5. go func 352280 goroutine count = 4
    6. go func 352281 goroutine count = 4
    7. go func 352282 goroutine count = 4
    8. go func 352283 goroutine count = 4
    9. go func 352284 goroutine count = 4
    10. go func 352285 goroutine count = 4
    11. go func 352286 goroutine count = 4
    12. go func 352287 goroutine count = 4
    13. go func 352288 goroutine count = 4
    14. go func 352289 goroutine count = 4
    15. go func 352290 goroutine count = 4
    16. go func 352291 goroutine count = 4
    17. go func 352292 goroutine count = 4
    18. go func 352293 goroutine count = 4
    19. go func 352294 goroutine count = 4
    20. go func 352295 goroutine count = 4
    21. go func 352296 goroutine count = 4
    22. go func 352297 goroutine count = 4
    23. go func 352298 goroutine count = 4
    24. go func 352299 goroutine count = 4
    25. go func 352300 goroutine count = 4
    26. go func 352301 goroutine count = 4
    27. go func 352302 goroutine count = 4
    28. ...

    从结果看,程序并没有出现崩溃,而是按部就班的顺序执行,并且go的数量控制在了3,(4的原因是因为还有一个main goroutine)那么从数字上看,是不是在跑的goroutines有几十万个呢?

    4、Go是否可以无限go? 如何限定数量? - 图2

    这里我们用了,buffer为3的channel, 在写的过程中,实际上是限制了速度。限制的是

    for循环的速度,因为这个速度决定了go的创建速度,而go的结束速度取决于 busi()函数的执行速度。 这样实际上,我们就能够保证了,同一时间内运行的goroutine的数量与buffer的数量一致。从而达到了限定效果。

    但是这段代码有一个小问题,就是如果我们把go_cnt的数量变的小一些,会出现打出的结果不正确。

    1. package main
    2. import (
    3. "fmt"
    4. //"math"
    5. "runtime"
    6. func busi(ch chan bool, i int) {
    7. fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
    8. <-ch
    9. }
    10. //模拟用户需求业务的数量
    11. //task_cnt := math.MaxInt64
    12. task_cnt := 10
    13. ch := make(chan bool, 3)
    14. for i := 0; i < task_cnt; i++ {
    15. ch <- true
    16. go busi(ch, i)
    17. }
    18. }

    结果

    1. go func 2 goroutine count = 4
    2. go func 3 goroutine count = 4
    3. go func 4 goroutine count = 4
    4. go func 5 goroutine count = 4
    5. go func 6 goroutine count = 4
    6. go func 1 goroutine count = 4
    7. go func 8 goroutine count = 4

    是因为main将全部的go开辟完之后,就立刻退出进程了。所以想全部go都执行,需要在main的最后进行阻塞操作。

    方法二:只使用sync同步机制

    1. import (
    2. "fmt"
    3. "math"
    4. "sync"
    5. "runtime"
    6. )
    7. var wg = sync.WaitGroup{}
    8. func busi(i int) {
    9. fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
    10. wg.Done()
    11. }
    12. func main() {
    13. //模拟用户需求业务的数量
    14. task_cnt := math.MaxInt64
    15. for i := 0; i < task_cnt; i++ {
    16. wg.Add(1)
    17. go busi(i)
    18. }
    19. wg.Wait()
    20. }

    很明显,单纯的使用sync依然达不到控制goroutine的数量,所以最终结果依然是崩溃。

    结果

    方法三:channel与sync同步组合方式

    1. package main
    2. import (
    3. "fmt"
    4. "math"
    5. "sync"
    6. "runtime"
    7. )
    8. var wg = sync.WaitGroup{}
    9. func busi(ch chan bool, i int) {
    10. fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
    11. <-ch
    12. wg.Done()
    13. }
    14. func main() {
    15. //模拟用户需求go业务的数量
    16. task_cnt := math.MaxInt64
    17. ch := make(chan bool, 3)
    18. for i := 0; i < task_cnt; i++ {
    19. wg.Add(1)
    20. ch <- true
    21. go busi(ch, i)
    22. }
    23. wg.Wait()
    24. }

    结果

    1. //...
    2. go func 228851 goroutine count = 4
    3. go func 228852 goroutine count = 4
    4. go func 228853 goroutine count = 4
    5. go func 228854 goroutine count = 4
    6. go func 228855 goroutine count = 4
    7. go func 228856 goroutine count = 4
    8. go func 228857 goroutine count = 4
    9. go func 228858 goroutine count = 4
    10. go func 228859 goroutine count = 4
    11. go func 228860 goroutine count = 4
    12. go func 228861 goroutine count = 4
    13. go func 228862 goroutine count = 4
    14. go func 228863 goroutine count = 4
    15. go func 228864 goroutine count = 4
    16. go func 228865 goroutine count = 4
    17. go func 228866 goroutine count = 4
    18. go func 228867 goroutine count = 4
    19. //...

    这样我们程序就不会再造成资源爆炸而崩溃。而且运行go的数量控制住了在buffer为3的这个范围内。

    方法四:利用无缓冲channel与任务发送/执行分离方式

    1. package main
    2. import (
    3. "fmt"
    4. "math"
    5. "sync"
    6. "runtime"
    7. )
    8. var wg = sync.WaitGroup{}
    9. func busi(ch chan int) {
    10. for t := range ch {
    11. fmt.Println("go task = ", t, ", goroutine count = ", runtime.NumGoroutine())
    12. wg.Done()
    13. }
    14. }
    15. func sendTask(task int, ch chan int) {
    16. wg.Add(1)
    17. ch <- task
    18. }
    19. func main() {
    20. ch := make(chan int) //无buffer channel
    21. goCnt := 3 //启动goroutine的数量
    22. for i := 0; i < goCnt; i++ {
    23. //启动go
    24. go busi(ch)
    25. }
    26. taskCnt := math.MaxInt64 //模拟用户需求业务的数量
    27. for t := 0; t < taskCnt; t++ {
    28. //发送任务
    29. sendTask(t, ch)
    30. }
    31. }

    结构

    执行流程大致如下,这里实际上是将任务的发送和执行做了业务上的分离。使得消息出去,输入SendTask的频率可设置、执行Goroutine的数量也可设置。也就是既控制输入(生产),又控制输出(消费)。使得可控更加灵活。这也是很多Go框架的Worker工作池的最初设计思想理念。


    以上便是目前有关限定goroutine基础设计思路。

    参考:

    http://team.jiunile.com/blog/2019/09/go-control-goroutine-number.html