最大数量的goroutines

时间:2011-12-14 17:43:50

标签: go multitasking goroutine

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

更新:我只是investigated in goroutines performance了一点,得到了这样的结果:

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

7 个答案:

答案 0 :(得分:46)

如果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

在安装了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)
}

答案 1 :(得分:15)

每次数以万计的常见问题解答:Why goroutines instead of threads?

  

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

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

要了解goroutine的最大数量,请注意每个goroutine的成本主要是堆栈。再次按常见问题解答:

  

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

包络回计算假设每个goroutine都有一个4 KiB page分配给堆栈(4 KiB是一个非常统一的大小),加上一些控制块的小开销(像运行时的​​Thread Control Block);这与你观察到的一致(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):

去1.2发行说明:Stack size

  

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

Go 1.4发行说明:Changes to the runtime

  

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

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

由于主要使用多个goroutine用于IO绑定任务(具体来说是处理阻塞系统调用,特别是网络或文件系统IO),因此您更有可能在其他资源(即网络套接字)上遇到操作系统限制或文件句柄:golang-nuts › The max number of goroutines and file descriptors?。解决这个问题的常用方法是使用pool稀缺资源,或者更简单地通过semaphore来限制数量;请参阅Conserving File Descriptors in GoLimiting Concurrency in Go

答案 2 :(得分:5)

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

答案 3 :(得分:5)

换句话说,有谎言,该死的谎言和基准。正如Erlang基准的作者承认的那样,

  

不用说,没有足够的内存了   机器实际做任何有用的事情。 stress-testing erlang

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

答案 4 :(得分:2)

这是Dave Cheney关于这个主题的精彩文章:http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite

答案 5 :(得分:0)

如果goroutine的数量成为问题,您可以轻松地将其限制为您的程序:
请参阅mr51m0n/gorcthis example

  

设置正在运行的goroutine数量的阈值

     

启动或停止goroutine时可以增加和减少计数器   它可以等待运行的最小或最大数量的goroutine,从而允许为同时运行的gorc受管goroutines的数量设置阈值。

答案 6 :(得分:0)

当操作是CPU 受限时,事实证明超出内核数量的任何事情都无济于事。

在任何其他情况,您都需要自我测试。