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