最大数量的goroutines

OCy*_*ril 62 go multitasking goroutine

我可以使用多少goroutines无痛?例如维基百科说,在Erlang中,可以创建2000万个进程,而不会降低性能.

更新:我刚刚调查了goroutines性能并得到了这样的结果:

  • 看起来goroutine的生命周期比计算sqrt()1000倍(对我来说~45μs)更多,唯一的限制是内存
  • Goroutine的成本为4 - 4.5 KB

小智 49

如果goroutine被阻止,除了以下情况外不涉及任何费用:

  • 内存使用情况
  • 慢垃圾收集

成本(在内存和实际开始执行goroutine的平均时间方面)是:

Go 1.6.2 (April 2016)
  32-bit x86 CPU (A10-7850K 4GHz)
    | Number of goroutines: 100000
    | Per goroutine:
    |   Memory: 4536.84 bytes
    |   Time:   1.634248 µs
  64-bit x86 CPU (A10-7850K 4GHz)
    | Number of goroutines: 100000
    | Per goroutine:
    |   Memory: 4707.92 bytes
    |   Time:   1.842097 µs

Go release.r60.3 (December 2011)
  32-bit x86 CPU (1.6 GHz)
    | Number of goroutines: 100000
    | Per goroutine:
    |   Memory: 4243.45 bytes
    |   Time:   5.815950 µs
Run Code Online (Sandbox Code Playgroud)

在安装了4 GB内存的计算机上,这会将最大goroutine数限制为略少于100万.


源代码(如果您已经了解上面打印的数字,则无需阅读此内容):

package main

import (
    "flag"
    "fmt"
    "os"
    "runtime"
    "time"
)

var n = flag.Int("n", 1e5, "Number of goroutines to create")

var ch = make(chan byte)
var counter = 0

func f() {
    counter++
    <-ch // Block this goroutine
}

func main() {
    flag.Parse()
    if *n <= 0 {
            fmt.Fprintf(os.Stderr, "invalid number of goroutines")
            os.Exit(1)
    }

    // Limit the number of spare OS threads to just 1
    runtime.GOMAXPROCS(1)

    // Make a copy of MemStats
    var m0 runtime.MemStats
    runtime.ReadMemStats(&m0)

    t0 := time.Now().UnixNano()
    for i := 0; i < *n; i++ {
            go f()
    }
    runtime.Gosched()
    t1 := time.Now().UnixNano()
    runtime.GC()

    // Make a copy of MemStats
    var m1 runtime.MemStats
    runtime.ReadMemStats(&m1)

    if counter != *n {
            fmt.Fprintf(os.Stderr, "failed to begin execution of all goroutines")
            os.Exit(1)
    }

    fmt.Printf("Number of goroutines: %d\n", *n)
    fmt.Printf("Per goroutine:\n")
    fmt.Printf("  Memory: %.2f bytes\n", float64(m1.Sys-m0.Sys)/float64(*n))
    fmt.Printf("  Time:   %f µs\n", float64(t1-t0)/float64(*n)/1e3)
}
Run Code Online (Sandbox Code Playgroud)

  • 您从~4k/per goroutine(已从发行版更改为发行版;并且还需要考虑goroutine堆栈使用情况)到基于安装的内存的maxium的转换是有缺陷的.最大值将基于较小的可寻址虚拟内存(32位操作系统通常为2-3GB),或物理内存*加*可用交换空间,或进程的内存资源限制(通常无限制).例如,在具有智能交换设置的64位机器上,安装的物理内存与任何*限制*无关(但随着交换开始发生,性能将下降). (2认同)
  • go操场报告每个goroutine的"2758.41字节",运行1.5.1. (2认同)

Nil*_*rth 16

成千上万,每个Go常见问题:为什么goroutines而不是线程?:

在同一地址空间中创建数十万个goroutine是切实可行的.

测试测试/ chan/goroutines.go创建10,000并且可以轻松做更多,但设计为快速运行; 您可以更改系统上的数字以进行试验.在给定足够内存的情况下,例如在服务器上,您可以轻松运行数百万.

要了解goroutine的最大数量,请注意每个goroutine的成本主要是堆栈.每个FAQ再次:

... goroutines,可以非常便宜:它们在堆栈的内存之外几乎没有开销,这只是几千字节.

包络背面计算假设每个goroutine都有一个4 KiB 页面为堆栈分配(4 KiB是一个非常统一的大小),加上一些控制块(如线程控制块)的小开销运行时; 这与你观察到的一致(2011年,前期Go 1.0).因此,100 Ki例程将占用大约400 MiB的内存,而1 Mi例程将占用大约4 GiB的内存,这仍然可以在桌面上管理,对于电话来说有点多,并且在服务器上非常易于管理.在实践中,起始堆栈的大小范围从半页(2 KiB)到两页(8 KiB),因此这大致是正确的.

起始堆栈大小随时间而变化; 它开始于4 KiB(一页),然后在1.2增加到8 KiB(2页),然后在1.4减少到2 KiB(半页).这些更改是由于分段堆栈导致在段之间快速切换时出现性能问题("热堆栈拆分"),因此增加以缓解(1.2),然后在使用连续堆栈替换分段堆栈时减少(1.4):

Go 1.2发行说明:堆栈大小:

在Go 1.2中,创建goroutine时堆栈的最小大小已从4KB提升到8KB

Go 1.4发行说明:对运行时的更改:

1.4中goroutine堆栈的默认起始大小已从8192字节减少到2048字节.

每个内存主要是堆栈,它开始低并且增长,所以你可以廉价地拥有许多goroutine.您可以使用较小的起始堆栈,但随后它必须更快地增长(以时间为代价获得空间),并且由于控制块不缩小而带来的好处减少.可以消除堆栈,至少在换出时(例如,在堆上进行所有分配,或者将堆栈保存到上下文切换中的堆),尽管这会损害性能并增加复杂性.这是可能的(如在Erlang中),意味着您只需要控制块和保存的上下文,允许另外因子为goroutines数量的5×-10×,现在受限于控制块大小和goroutine的堆上大小 - 局部变量.然而,这并不是非常有用,除非你需要数以百万计的小睡眠goroutines.

由于主要使用多个goroutine用于IO绑定任务(具体来说是处理阻塞系统调用,特别是网络或文件系统IO),因此您更有可能在其他资源上遇到操作系统限制,即网络套接字或文件句柄:golang-nuts> goroutines和文件描述符的最大数量?.解决这个问题的常用方法是使用稀缺资源,或者更简单地通过信号量来限制数量; 请参阅Go中的保存文件描述符Go中的限制并发性.


jim*_*imt 5

这完全取决于您运行的系统.但是goroutines非常轻巧.一个普通的进程应该没有100.000并发例程的问题.当然,这是否适用于您的目标平台,如果不知道该平台是什么,我们无法回答这些问题.

  • 你是脱离背景的.句子写着'平均过程应该没有100,000个并发例程的问题'. (3认同)
  • 换句话说,如果没有适当的上下文,您的主张“100.000 个并发例程没有问题”是毫无意义的。 (2认同)

pet*_*rSO 5

换言之,有谎言,该死的谎言和基准.正如Erlang基准的作者承认的那样,

不言而喻,机器中没有足够的内存来实际执行任何有用的操作.压力测试erlang

什么是您的硬件,您的操作系统是什么,您的基准源代码在哪里?试图衡量和证明/反驳的基准是什么?