Go-控制协程数量

背景

GMP的无限创建Goroutine基于共享用户态资源,过多的协程会导致CPU利用率浮动上涨、内存占用上涨、主进程崩溃

如何控制Goroutine数量

基于buffer的channel

原理:通过buffer的缓冲区的大小和阻塞等待来控制最大的数量

package main

import (
"fmt"
"runtime"
"time"
)

func doGoroutine(i int, ch chan bool) {
fmt.Println("go func", i, "goroutine count", runtime.NumGoroutine())
// 结束了一个任务
<-ch
}
func main() {
task_cnt := 10
// 容量控制了 Goroutine 的数量
ch := make(chan bool, 3)
// for的数据决定了Goroutine的创建速度
for i := 0; i < task_cnt; i++ {
ch <- true
go doGoroutine(i, ch)
}
// task_cnt 数量太小,主线程会优先退出,需要阻塞等待
time.Sleep(100 * time.Second)
}

结合sync同步机制

如果单独使用sync机制,耗费速度小于Go生产速度,GO还是会出现数量溢出

package main

import (
"fmt"
"runtime"
"sync"
)

/*
基于sync机制 和buffer channel实现最大协程数量控制
*/
var wg sync.WaitGroup

func doGoroutine(i int, ch chan bool) {
fmt.Println("go func", i, "goroutine count", runtime.NumGoroutine())
// 结束了一个任务
wg.Done()
<-ch
}
func main() {
task_cnt := 10
ch := make(chan bool, 3)
for i := 0; i < task_cnt; i++ {
wg.Add(1)
ch <- true
go doGoroutine(i, ch)
}
wg.Wait()
}

基于无buffer 的channel分离机制

package main

import (
"fmt"
"math"
"runtime"
"sync"
)

/*
基于无buffer 的channel分离机制
*/
func doGoroutine(ch chan int) {
for task := range ch {
fmt.Println("go task", task, "goroutine count", runtime.NumGoroutine())
// 结束了一个任务
wg.Done()
}

}
func sendTask(task int, ch chan int) {
wg.Add(1)
// 任务发给channel
ch <- task
}

var wg sync.WaitGroup

func main() {
//无buffer channel
ch := make(chan int)
// Goroutine数量
goCnt := 3
for i := 0; i < goCnt; i++ {
go doGoroutine(ch)
}
// 业务数量
taskCnt := math.MaxInt
for i := 0; i < taskCnt; i++ {
sendTask(i, ch)
}
wg.Wait()
}