Максимальное количество горутин

сколько 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 работает в то же время.