痛みのないゴルーチンをいくつ使用できますか?たとえば、ウィキペディアによると、Erlangでは、パフォーマンスを低下させることなく、2,000万のプロセスを作成できます。
更新:私はちょうど ゴルーチンのパフォーマンスで調査しました 少しとそのような結果を得ました:
ゴルーチンがブロックされている場合、以下の費用がかかります:
コスト(メモリおよびゴルーチンの実行を実際に開始するまでの平均時間)は次のとおりです。
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のメモリが搭載されたマシンでは、これによりゴルーチンの最大数が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)
}
Go FAQごとに数十万人: なぜスレッドの代わりにゴルーチン? :
同じアドレス空間に数十万のゴルーチンを作成するのが実用的です。
Test test/chan/goroutines.go は10,000を作成し、簡単にそれ以上を実行できますが、迅速に実行するように設計されています。システムの番号を変更して実験することができます。サーバー上などに十分なメモリがあれば、簡単に数百万を実行できます。
ゴルーチンの最大数を理解するために、ゴルーチンごとのコストは主にスタックであることに注意してください。 FAQ再び:
…goroutinesは非常に安価です。スタックのメモリ(わずか数キロバイト)を超えるオーバーヘッドはほとんどありません。
エンベロープの計算では、各ゴルーチンに1つの4 KiB page がスタックに割り当てられていると想定しています(4 KiBはかなり均一なサイズです)。さらに、制御ブロックの小さなオーバーヘッド(ランタイムの Thread Control Block )など。これは、あなたが観察したものと一致します(2011年、Go 1.0より前)。したがって、100 Kiルーチンは約400 MiBのメモリを使用し、1 Miルーチンは約4 GiBのメモリを使用します。実際には、開始スタックのサイズは半ページ(2 KiB)から2ページ(8 KiB)の範囲であるため、これはほぼ正しいです。
開始時のスタックサイズは時間とともに変化しました。 4 KiB(1ページ)から始まり、1.2では8 KiB(2ページ)に増加し、1.4では2 KiB(半ページ)に減少しました。これらの変更は、セグメント間で迅速に切り替え(「ホットスタックスプリット」)するときにパフォーマンスの問題を引き起こすセグメント化されたスタックによるものでした。
Go 1.2リリースノート: スタックサイズ :
Go 1.2では、ゴルーチンが作成されるときのスタックの最小サイズが4KBから8KBに引き上げられました
Go 1.4リリースノート: ランタイムの変更 :
1.4でのゴルーチンのスタックのデフォルトの開始サイズは、8192バイトから2048バイトに削減されました。
ゴルーチンごとのメモリの大部分はスタックであり、メモリの開始数は少なくなり、成長するため、多くのゴルーチンを安価に使用できます。より小さな開始スタックを使用することもできますが、それより早く成長する必要があり(時間のコストでスペースを獲得します)、制御ブロックが縮小しないために利点が減少します。少なくともスワップアウトされた場合(たとえば、ヒープですべての割り当てを行うか、コンテキストスイッチでスタックをヒープに保存する)、スタックを削除することは可能ですが、これによりパフォーマンスが低下し、複雑さが増します。これは(Erlangのように)可能であり、制御ブロックと保存されたコンテキストが必要なだけで、ゴルーチンの数が5×10倍になることを意味します。ゴルーチンの制御ブロックサイズとヒープサイズによって制限されます。 -ローカル変数。しかし、これは何百万もの小さな眠っているゴルーチンを必要としない限り、それほど便利ではありません。
多くのゴルーチンを使用する主な用途は、IOにバインドされたタスク(具体的には、ネットワークまたはファイルシステムIOのブロッキングを処理するため)であるため、他のリソース、つまりネットワークソケットまたはファイルハンドルのOS制限に遭遇する可能性が非常に高くなります: golang-nuts›ゴルーチンとファイル記述子の最大数? 。これに対処する通常の方法は、乏しいリソースの pool を使用するか、単に semaphore ;を介して数を制限することです。 Goでのファイル記述子の保存 および Goでの同時実行の制限 を参照してください。
それは実行中のシステムに完全に依存します。しかし、ゴルーチンは非常に軽量です。平均的なプロセスでは、100.000の並行ルーチンで問題は発生しません。これがターゲットプラットフォームに当てはまるかどうかは、もちろん、そのプラットフォームが何であるかを知ることなく答えることはできません。
言い換えれば、嘘、いまいましい嘘、そしてベンチマークがあります。 Erlangベンチマークの作者が告白したように、
言うまでもなく、実際に役立つことをするのに十分なメモリがマシンに残っていませんでした。 ストレステストアーラン
ハードウェア、オペレーティングシステム、ベンチマークソースコードはどこですか?測定および証明/反証しようとしているベンチマークは何ですか?
このトピックに関するDave Cheneyのすばらしい記事を次に示します。 http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite
ゴルーチンの数が問題になる場合は、プログラムで簡単に制限できます。
mr51m0n/gorc および この例 を参照してください。
実行中のゴルーチンの数にしきい値を設定します
ゴルーチンを開始または停止するときにカウンターを増減できます。
最小または最大数のゴルーチンが実行されるのを待つことができるため、同時に実行されるgorc
制御ゴルーチンの数にしきい値を設定できます。