1.背景
一次在实际开发过程中,由于在for循环中不断创建 go协程,导致当go协程数量达到一定的数量时候程序直接被系统kill掉了。因此本文就想探究一下在go中是否能无限创建go协程,如果不能,那么能创建最大的协程数量是多少。
2. 协程可以无限创建吗?
首先我们可以在for循环中不断创建go协程看看能创建多少个go协程后进程被kill
import (
"fmt"
"math"
"runtime"
)
func main() {
taskCount := math.MaxInt64
for i := 0; i < taskCount; i++ {
go func(i int) {
fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
}(i)
}
}
运行结果:
结果可以看到,当协程最大数量达到29900时候程序最终会被系统强制 kill 掉,强制结束进程。
如果我们大量的开启 goroutine 会占满某一时间操作系统上用户态程序共享的资源,其中包括 CPU、Memory、Fd 等。从而导致系统瘫痪甚至影响其他程序。
- CPU 使用率瞬间上涨
- Memory 占用不断上涨
- 主进程崩溃,强制 Kill
- 不同机器的能够创建的最大协程数量是不一样的
3 如何控制 goroutine 数量
3.1 通过 buffer channl 来控制 goroutine
import (
"fmt"
"runtime"
)
func work(ch chan bool, i int) {
fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
<-ch
}
func main() {
taskCount := 10
ch := make(chan bool, 3)
for i := 0; i < taskCount; i++ {
ch <- true
go work(ch, i)
}
}
程序运行结果:
解读下代码,这里我们用了 3个 channel 对应 3 个 goroutine 执行任务。在同一时间内运行的 goroutine 的数量与 channel 限制 buffer 的数量是一致的,从而达到限制 goroutine 的效果。
3.2 通过 sync.WaitGroup 来控制 goroutine
import (
"fmt"
"math"
"sync"
"runtime"
)
运行结果:
从运行结果可以看出,进程还是被操作系统强制 Kill 了,使用 sync.WaitGroup{} 并不能控制 goroutine 的数量。
3.3 channel & sync.WaitGroup 同步组合方式
import (
"fmt"
"math"
"sync"
"runtime"
)
var wg = sync.WaitGroup{}
func work(ch chan bool, i int) {
fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
<-ch
wg.Done()
}
func main() {
//模拟用户需求go业务的数量
taskCount := math.MaxInt64
ch := make(chan bool, 3)
for i := 0; i < taskCount; i++ {
wg.Add(1)
ch <- true
go work(ch, i)
}
wg.Wait()
}
运行结果:
进程没有被操作系统 Kill,通过 buffer channel 这种控制住了 goroutine 数量。
3.4 无 buffer channel 控制 goroutine 数量
import (
"fmt"
"sync"
"runtime"
)
var wg = sync.WaitGroup{}
func work(ch chan int) {
for i := range ch {
fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
wg.Done()
}
}
func sendTask(task int, ch chan int) {
wg.Add(1)
ch <- task
}
func main() {
// 无 buffer channel
ch := make(chan int)
goCount := 3
for i := 0; i < goCount; i++ {
// 启动go
go busi(ch)
}
taskCount := 10
for t := 0; t < taskCount; t++ {
// 发送任务
sendTask(t, ch)
}
wg.Wait()
}
运行结果:
首先创建了无 buffer 的 channel,将任务发送到 channel 中,通过控制 goroutine 数量的方式执行程序,达到控制 goroutine。
3.5 协程池方式控制 goroutine
如果对go协程的创建数量不加以限制,那么最终会导致创建的go协程数量膨胀,CPU负载过高,内存和文件描述符fd占用过多导致进程最终出现异常停止,因此我们可以借助协程池去管理控制go协程的数量。可以参考:https://github.com/bytedance/gopkg/tree/develop/util/gopool
4.总结
从以上可以知道,在实际开发过程中,对于轻量级的go协程并发,我们也需要注意控制go协程的数量,避免对我们的服务造成影响。
Comments | NOTHING