关于后端:控制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)
    }
}

执行后,很快报错

`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多平台公布

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理