控制goroutine 的并发执行数量

goroutine的数量上限是1048575吗?

正常项目,协程数量超过十万就需要引起重视。如果有上百万goroutine,一般是有问题的。

但并不是说协程数量的上限是100多w

1048575的来自类似如下的demo代码:

go 复制代码
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:

go 复制代码
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

或者如下:

go 复制代码
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)
 }
}
go 复制代码
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 释放缓冲区。

go 复制代码
// 通过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)
 }
}
makefile 复制代码
当前协程数: 301
当前协程数: 301
当前协程数: 301
当前协程数: 301
当前协程数: 301
当前协程数: 301
当前协程数: 301
当前协程数: 301
当前协程数: 301
...

同时只有301个协程(每15s,处理301个;限制太少,会大大增加程序执行完成需要的时间,具体限制多少,需要权衡,太大太小可能都有问题)

更多参考:

如何控制golang协程的并发数量问题1

golang实现并发数控制的方法2

golang控制并发数3

Golang的并发控制4

即所谓的

无缓冲的channel可以当成阻塞锁来使用Go用两个协程交替打印100以内的奇偶数

有缓冲的channel通常可以用来控制goroutine的数量

来,控制一下 goroutine 的并发数量5

还有通过协程池,信号量等方式,可参考 【警惕】请勿滥用goroutine6

aceld-Go是否可以无限go?如何限定数量?7

参考资料

1

如何控制golang协程的并发数量问题: www.manongjc.com/detail/62-i...

2

golang实现并发数控制的方法: www.qb5200.com/article/327...

3

golang控制并发数: blog.csdn.net/weixin_3815...

4

Golang的并发控制: blog.csdn.net/LINZEYU666/...

5

来,控制一下 goroutine 的并发数量: eddycjy.gitbook.io/golang/di-1...

6

【警惕】请勿滥用goroutine: juejin.cn/post/699980...

7

aceld-Go是否可以无限go?如何限定数量?: github.com/catandcoder...

相关推荐
YuePeng18 小时前
凌晨 3 点告警群炸了,我用浏览器干了原本 XShell 才能干的事
后端·github
染翰18 小时前
Nacos 切换 Namespace 后配置不生效、占位符报错终极复盘
java·后端·spring·nacos
2601_9516457819 小时前
Linux 编程语言全解析:C、C++、Python、Go、Rust 谁更强?
linux·python·go·c·编程语言
阿正的梦工坊19 小时前
【Rust】19-FFI、ABI 与跨语言边界设计
开发语言·后端·rust
fox_lht19 小时前
第十五章 函数式语言:迭代器和闭包
开发语言·后端·学习·算法·rust
码不停蹄的玄黓19 小时前
Spring Boot 实现过滤器(Filter)三种常用方式
java·spring boot·后端
悟空瞎说19 小时前
PM2 最全常用命令详解
后端
长栎19 小时前
你每次 git commit 都在用设计模式,但你可能一个都没认出来
后端
长栎19 小时前
HikariCP 源码里的设计模式,比连接池本身更值得学
后端
ViavaCos19 小时前
AI 帮我写代码,我帮 AI 踩坑:Vue 大数据表格优化全记录
前端·性能优化