goroutine 的数量下限是 1048575 吗?
失常我的项目,协程数量超过十万就须要引起器重。如果有上百万 goroutine,个别是有问题的。
但并不是说协程数量的下限是 100 多 w
1048575 的来自相似如下的 demo 代码:
package main
import (
"fmt"
"math"
"runtime"
"time"
)
// https://zhuanlan.zhihu.com/p/568151296
func main() {
maxCount := math.MaxInt64
for i := 0; i < maxCount; i++ {go func(i int) {fmt.Printf("i is: %d,goroutine num: %d\n", i, runtime.NumGoroutine())
// 模仿各种耗时较长的业务逻辑
time.Sleep(10 * time.Second)
}(i)
}
}
执行后,很快报错
`panic: too many concurrent operations on a single file or socket (max 1048575)
`
但这个是因为 fmt.Printf 导致的:
对单个 file/socket 的并发操作数超过了零碎下限,这个是规范输入造成的,具体一点,就是文件句柄数量达到限度
如下例子,去掉 fmt:
package main
import (
"fmt"
"math"
"runtime"
"time"
)
func main() {
maxCount := math.MaxInt64
for i := 0; i < maxCount; i++ {go func(i int) {
// 模仿各种耗时较长的业务逻辑
//time.Sleep(10 * time.Hour)
time.Sleep(15 * time.Second)
if i > 1300_0000 {//if runtime.NumGoroutine() > 1000_0000 {fmt.Println("以后协程数:", runtime.NumGoroutine())
}
}(i)
}
}
理论同一时间能够呈现 1000w 的 goroutine,可见 goroutine 的实践下限相对不止 100w
或者如下:
package main
import (
"fmt"
"math"
"runtime"
"time"
)
func main() {
maxCount := math.MaxInt64
for i := 0; i < maxCount; i++ {go func(i int) {
// 模仿各种耗时较长的业务逻辑
//time.Sleep(10 * time.Hour)
time.Sleep(15 * time.Second)
//if i > 1300_0000 {if runtime.NumGoroutine() > 800_0000 {fmt.Println("以后协程数:", runtime.NumGoroutine())
}
}(i)
}
}
panic: too many concurrent operations on a single file or socket (max 1048575)
goroutine 1231546 [running]:
internal/poll.(*fdMutex).rwlock(0x140000a2060, 0x20?)
/Users/fliter/.g/go/src/internal/poll/fd_mutex.go:147 +0x134
internal/poll.(*FD).writeLock(...)
/Users/fliter/.g/go/src/internal/poll/fd_mutex.go:239
internal/poll.(*FD).Write(0x140000a2060, {0x14635532bc0, 0x19, 0x20})
/Users/fliter/.g/go/src/internal/poll/fd_unix.go:370 +0x48
os.(*File).write(...)
/Users/fliter/.g/go/src/os/file_posix.go:48
os.(*File).Write(0x140000a0008, {0x14635532bc0?, 0x19, 0x10412e25c?})
/Users/fliter/.g/go/src/os/file.go:175 +0x60
fmt.Fprintln({0x104168cf8, 0x140000a0008}, {0x140bde92f88, 0x2, 0x2})
/Users/fliter/.g/go/src/fmt/print.go:285 +0x74
fmt.Println(...)
/Users/fliter/.g/go/src/fmt/print.go:294
main.main.func1(0x0?)
/Users/fliter/go/src/shuang/0000/goNum.go:20 +0x150
created by main.main
/Users/fliter/go/src/shuang/0000/goNum.go:14 +0x54
exit status 2
比拟奇怪的是,如果将 模仿各种耗时较长的业务逻辑 的time.Sleep(15 * time.Second)
改为time.Sleep(10 * time.Hour)
,最终会因为内存过高而signal: killed
。但此时 goroutine 数量不够多,触发不了 if 外面的 fmt 逻辑,故而不会呈现panic: too many concurrent operations on a single file or socket (max 1048575)
(而休眠 10 几 s 的代码,内存到不了这么大,就曾经因为 fmt 的问题 panic 了)
管制形式
应用有缓冲的 channel,限度并发的协程数量
make(chan struct{}, 300) 创立缓冲区大小为 300 的 channel,在没有被接管的状况下,至少发送 300 个音讯则被阻塞。
开启协程前,调用 ch <- struct{}{},若缓存区满,则阻塞。
协程工作完结,调用 <-ch 开释缓冲区。
// 通过 channel 来管制并发数
package main
import (
"fmt"
"math"
"runtime"
"time"
)
func main() {ch := make(chan struct{}, 300)
maxCount := math.MaxInt64
for i := 0; i < maxCount; i++ {ch <- struct{}{}
go func(i int) {//fmt.Printf("i is: %d,go func num: %d\n", i, runtime.NumGoroutine())
// 模仿各种耗时较长的业务逻辑
//time.Sleep(10 * time.Hour)
time.Sleep(15 * time.Second)
//if i > 1000_0000 {//if runtime.NumGoroutine() > 1000_0000 {fmt.Println("以后协程数:", runtime.NumGoroutine())
//}
// 读取 channel 数据
<-ch
}(i)
}
}
以后协程数: 301
以后协程数: 301
以后协程数: 301
以后协程数: 301
以后协程数: 301
以后协程数: 301
以后协程数: 301
以后协程数: 301
以后协程数: 301
...
同时只有 301 个协程(每 15s,解决 301 个;限度太少,会大大增加程序执行实现须要的工夫,具体限度多少,须要衡量,太大太小可能都有问题)
更多参考:
如何管制 golang 协程的并发数量问题
golang 实现并发数管制的办法
golang 管制并发数
Golang 的并发管制
即所谓的
无缓冲的 channel 能够当成阻塞锁来应用([Go 用两个协程交替打印 100 以内的奇偶数]())
有缓冲的 channel 通常能够用来管制 goroutine 的数量
来,管制一下 goroutine 的并发数量
还有通过协程池,信号量等形式,可参考【警觉】请勿滥用 goroutine
aceld-Go 是否能够有限 go?如何限定数量?
本文由 mdnice 多平台公布