Максимальное количество горутин
сколько goroutines я могу использовать безболезненно? Например, Википедия говорит, что в Erlang 20 миллионов процессов могут быть созданы без ухудшения производительности.
обновление: Я исследовано в goroutines производительности немного и получил такой результат:
- похоже, что время жизни goroutine больше, чем вычисление sqrt () 1000 раз (~45μs для меня), единственное ограничение-память
- Goroutine стоит 4-4.5 КБ
6 ответов
Если 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 ГБ это ограничивает максимальное количество goroutines до чуть менее 1 миллиона.
исходный код (нет необходимости читать это, если вы уже поймите цифры, напечатанные выше):
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)
}
сотни тысяч, per Go FAQ:почему goroutines вместо потоков?:
практически создать сотни тысяч goroutines в таком же адресном пространстве.
тест test / chan / goroutines.go создает 10,000 и может легко сделать больше, но предназначен для быстрого запуска; вы можете изменить номер в вашей системе, чтобы экспериментировать. Вы можете легко запустить миллионы, учитывая достаточно памяти, например, на сервер.
чтобы понять максимальное количество goroutines, обратите внимание, что стоимость за-goroutine в первую очередь стек. В FAQ снова:
...goroutines, могут быть очень дешевыми: у них мало накладных расходов за пределами памяти для стека, что составляет всего несколько килобайт.
бэк-оф-конверт расчетов предположить, что каждый горутина имеет один 4 Кб страница выделено для стека (4 Кб-довольно равномерный размер), плюс некоторые небольшие накладные расходы для блока управления (например,Блок Управления Потоком) для среды выполнения; это согласуется с тем, что вы наблюдали (в 2011 году, pre-Go 1.0). Таким образом, 100 процедур Ki заняли бы около 400 MiB памяти, а 1 Mi процедуры заняли бы около 4 GiB памяти, которая все еще управляема на рабочем столе, немного больше для телефона и очень управляема на сервере. На практике начальный стек имеет размер от половины страницы (2 Кб) до двух страниц (8 Кб), поэтому это примерно правильный.
размер стартового стека со временем изменился; он начался с 4 Кб (одна страница), затем в 1.2 был увеличен до 8 КБ (2 страницы), затем в 1.4 был уменьшен до 2 Кб (половина страницы). Эти изменения были вызваны сегментированными стеками, вызывающими проблемы с производительностью при быстром переключении между сегментами ("раскол горячего стека"), поэтому увеличились для смягчения (1.2), а затем уменьшились, когда сегментированные стеки были заменены смежными стеками (1.4):
Go 1.2 Релиз Примечания:размер стека:
в Go 1.2 минимальный размер стека при создании goroutine был поднят с 4KB до 8KB
Go 1.4 Примечания К Выпуску:изменения во время работы:
начальный размер по умолчанию для стека goroutine в 1.4 был уменьшен с 8192 байт до 2048 байт.
Per-goroutine память в основном стек, и он начинается низко и растет, так что вы можете дешево есть много горутин. Вы могли бы использовать меньший стартовый стек, но тогда он должен был бы расти раньше (получить пространство за счет времени), а преимущества уменьшаются из-за того, что блок управления не сжимается. Можно устранить стек, по крайней мере, при замене (например, сделать все выделение в куче или сохранить стек в кучу при переключении контекста), хотя это вредит производительности и добавляет сложности. Это возможно (как в Erlang), и означает, что вам просто нужен блок управления и сохраненный контекст, позволяющий еще один фактор 5×-10× в количестве goroutines, ограниченный теперь размером блока управления и размером на куче goroutine-локальные переменные. Однако, это не очень полезно, если вы не нужны миллионы крошечных спящих горутин.
поскольку основное использование многих goroutines предназначено для задач, связанных с IO (конкретно для обработки блокирующих syscalls, в частности, network или file system IO), вы гораздо чаще столкнетесь с ограничениями ОС на других ресурсах, а именно сетевых сокетах или дескрипторах файлов: golang-nuts " максимальное количество goroutines и дескрипторов файлов?. Обычный способ решить эту проблему-с помощью бассейн дефицитного ресурса, или более просто путем просто ограничивать номер через семафор; см. сохранение дескрипторов файлов в Go и ограничение параллелизма в Go.
Это полностью зависит от системы, которую вы работаете. Но горутины очень легкий. Средний процесс не должен иметь проблем с 100.000 параллельными подпрограммами. Идет ли это для вашей целевой платформы, конечно, то, что мы не можем ответить, не зная, что это за платформа.
перефразируя, есть ложь, проклятая ложь и критерии. Как признался автор эталона Эрланга,
само собой разумеется, что в памяти осталось недостаточно памяти машина на самом деле сделать что-нибудь полезное. стресс-тестирование erlang
что ваше оборудование, что ваша операционная система, где ваш исходный код бенчмарка? Что является эталоном, который пытается измерить и доказать/опровергнуть?
вот отличная статья Дэйва Чейни на эту тему:http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite
Если количество goroutine когда-либо станет проблемой, вы легко можете ограничить его для своей программы:
См.mr51m0n / gorc и .
установить пороговые значения по количеству запущенных goroutines
может увеличивать и уменьшать счетчик при запуске или остановке goroutine.
Он может ждать минимального или максимального количества запущенных goroutines, что позволяет установить пороговые значения для количестваgorc
управляемые goroutines работает в то же время.