控制goroutine 的并发执行数量

简介: 控制goroutine 的并发执行数量

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)
 }
}

执行后,很快报错

微信截图_20230925171240.png

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)
 }
}

微信截图_20230925171322.png

实际同一时间可以出现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)
 }
}

微信截图_20230925171408.png

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)

微信截图_20230925171450.png

(而休眠10几s的代码,内存到不了这么大,就已经因为fmt的问题panic了)

微信截图_20230925171503.png

控制方式

使用有缓冲的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协程的并发数量问题[1]

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

golang控制并发数[3]

Golang的并发控制[4]

即所谓的

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

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

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

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

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…


目录
相关文章
|
1月前
|
测试技术 API
如何精确控制 asyncio 中并发运行的多个任务
如何精确控制 asyncio 中并发运行的多个任务
48 2
|
5月前
|
Java 程序员
Java多线程编程是指在一个进程中创建并运行多个线程,每个线程执行不同的任务,并行地工作,以达到提高效率的目的
【6月更文挑战第18天】Java多线程提升效率,通过synchronized关键字、Lock接口和原子变量实现同步互斥。synchronized控制共享资源访问,基于对象内置锁。Lock接口提供更灵活的锁管理,需手动解锁。原子变量类(如AtomicInteger)支持无锁的原子操作,减少性能影响。
46 3
|
Linux 编译器 调度
【线程概念和线程控制】(一)
【线程概念和线程控制】(一)
125 0
|
6月前
|
Java
【Java】有 A、B、C 三个线程,如何保证三个线程同时执行?在并发情况下,如何保证三个线程依次执行?如何保证三个线程有序交错执行?
【Java】有 A、B、C 三个线程,如何保证三个线程同时执行?在并发情况下,如何保证三个线程依次执行?如何保证三个线程有序交错执行?
79 0
线程执行顺序怎么控制?
线程执行顺序怎么控制?
48 0
|
Java 程序员
同步模式之顺序控制线程执行
同步模式是指在多线程编程中,为了保证线程之间的协作和正确性,需要对线程的执行顺序进行控制。顺序控制线程执行是一种同步模式,它通过控制线程的等待和唤醒来实现线程的有序执行。
139 0
同步模式之顺序控制线程执行
|
Java 调度
进程和线程、实现多线程的两种方式、设置获取线程名称、线程优先级及线程控制
进程和线程、实现多线程的两种方式、设置获取线程名称、线程优先级及线程控制的简单示例
135 1
进程和线程、实现多线程的两种方式、设置获取线程名称、线程优先级及线程控制
|
Java
多线程相关面试题:并行和并发的区别、线程和进程、线程的创建方式、运行状态
多线程相关面试题:并行和并发的区别、线程和进程、线程的创建方式、运行状态
132 0
|
区块链
Goroutine 最大数量的限制
Goroutine 最大数量的限制
156 0