Golang无限开启Goroutine?该如何限定Goroutine数量?
Goroutine;Golang;Go
- 体积轻量(占内存小,一个 2kb 左右)
- 优秀的 GMP 调度
我们如果迅速的开启 goroutine (不控制并发的 goroutine 数量 )的话,会在短时间内占据操作系统的资源(CPU、内存、文件描述符等)。
- CPU 使用率浮动上涨
- Memory 占用不断上涨
- 主进程崩溃(被杀掉了)
二、控制 goroutine 的几种方法
方法一:用有 buffer 的 channel 来限制
package main
import (
"fmt"
"math"
"runtime"
)
// 模拟执行业务的 goroutine
// runtime.NumGoroutine()表示获取协程的数量
func doBusiness(ch chan bool, i int) {
fmt.Println("i的值:", i, "协程数:", runtime.NumGoroutine())
<-ch
}
func main() {
max := math.MaxInt64
fmt.Println(max)
ch := make(chan bool, 3)
for i := 0; i < max; i++ {
ch <- true
go doBusiness(ch, i)
}
}
打印的结果:
...
i的值: 101058 协程数: 4
i的值: 101059 协程数: 4
i的值: 101060 协程数: 4
i的值: 101061 协程数: 4
i的值: 101062 协程数: 4
i的值: 101063 协程数: 4
i的值: 101064 协程数: 4
i的值: 101065 协程数: 4
i的值: 101066 协程数: 4
i的值: 101067 协程数: 4
i的值: 101068 协程数: 4
i的值: 101069 协程数: 4
i的值: 101070 协程数: 4
i的值: 101071 协程数: 4
i的值: 101072 协程数: 4
i的值: 101073 协程数: 4
i的值: 101074 协程数: 4
i的值: 101075 协程数: 4
...
从结果看,程序并没有出现崩溃,而是按部就班的顺序执行,并且 go 的数量控制在了 3,(4 的原因是因为还有一个 main goroutine)
但是这段代码有一个小问题,就是如果我们把 go_cnt 的数量变的小一些,会出现打出的结果不正确。
package main
import (
"fmt"
"runtime"
)
// 模拟执行业务的 goroutine
// runtime.NumGoroutine()表示获取协程的数量
func doBusiness(ch chan bool, i int) {
fmt.Println("i的值:", i, "协程数:", runtime.NumGoroutine())
<-ch
}
func main() {
//max := math.MaxInt64
max := 10
fmt.Println(max)
ch := make(chan bool, 3)
for i := 0; i < max; i++ {
ch <- true
go doBusiness(ch, i)
}
}
结果:
10
i的值: 0 协程数: 2
i的值: 1 协程数: 2
i的值: 2 协程数: 2
i的值: 3 协程数: 2
i的值: 4 协程数: 2
i的值: 5 协程数: 2
i的值: 6 协程数: 2
i的值: 7 协程数: 2
i的值: 8 协程数: 2
可以从上面的实例中看出来有些 goroutine 没有打印出来,是由于 main 把所有 goroutine 开启之后,main 就直接退出了,我们知道 main 进程退出,低下所有的 goroutine 都会结束掉,从而导致有些 goroutine 还没来得及执行就退出了。所以想全部 go 都执行,需要在 main 的最后进行阻塞操作。
方法二:使用 sync 同步机制
package main
import (
"fmt"
"math"
"runtime"
"sync"
)
var wg = sync.WaitGroup{}
func doBusiness(i int) {
fmt.Println("i的值 ", i, " 协程的数量为 = ", runtime.NumGoroutine())
wg.Done()
}
func main() {
//模拟用户需求业务的数量
max := math.MaxInt64
for i := 0; i < max; i++ {
wg.Add(1)
go doBusiness(i)
}
wg.Wait()
}
很明显,如果单纯的使用 sync 也达不到控制 goroutine 的数量,最终结果依然是崩溃。
方法三:channel 与 sync 同步组合方式实现控制 goroutine
package main
import (
"fmt"
"math"
"runtime"
"sync"
)
var wg = sync.WaitGroup{}
func doBusiness(ch chan bool, i int) {
fmt.Println("i的值为 ", i, " 协程数量为 = ", runtime.NumGoroutine())
<-ch
wg.Done()
}
func main() {
//模拟用户需求go业务的数量
max := math.MaxInt64
ch := make(chan bool, 3)
for i := 0; i < max; i++ {
wg.Add(1)
ch <- true
go doBusiness(ch, i)
}
wg.Wait()
}
方法四:利用无缓冲 channel 与任务发送/执行分离方式
package main
import (
"fmt"
"math"
"runtime"
"sync"
)
var wg = sync.WaitGroup{}
func doBusiness(ch chan int) {
for t := range ch {
fmt.Println("go task = ", t, ", goroutine count = ", runtime.NumGoroutine())
wg.Done()
}
}
func sendTask(task int, ch chan int) {
wg.Add(1)
ch <- task
}
func main() {
ch := make(chan int) //无buffer channel
goCnt := 3 //启动goroutine的数量
for i := 0; i < goCnt; i++ {
//启动go
go doBusiness(ch)
}
taskCnt := math.MaxInt64 //模拟用户需求业务的数量
for t := 0; t < taskCnt; t++ {
//发送任务
sendTask(t, ch)
}
wg.Wait()
}