我可以使用多少goroutines无痛?例如维基百科说,在Erlang中,可以创建2000万个进程,而不会降低性能。
更新:我只是investigated in goroutines performance了一点,得到了这样的结果:
答案 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 Go和Limiting 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/gorc和this example。
设置正在运行的goroutine数量的阈值
启动或停止goroutine时可以增加和减少计数器 它可以等待运行的最小或最大数量的goroutine,从而允许为同时运行的
gorc
受管goroutines的数量设置阈值。
答案 6 :(得分:0)