Максимальное количество горутин - PullRequest
59 голосов
/ 14 декабря 2011

Сколько горутин можно использовать безболезненно?Например, википедия говорит, что в Erlang 20 миллионов процессов могут быть созданы без снижения производительности.

Обновление: Я только немного исследовал производительность в goroutines и получил такиеa результаты:

  • Похоже, что время жизни goroutine больше, чем вычисление sqrt () 1000 раз (для меня ~ 45 мкс), единственным ограничением является память
  • Стоимость Goroutine 4 - 4,5 КБ.

Ответы [ 6 ]

47 голосов
/ 16 декабря 2011

Если программа заблокирована, никаких дополнительных затрат, кроме:

, не требуется.
  • использование памяти
  • более медленный сборщик мусора

Затраты (с точки зрения памяти и среднего времени для фактического начала выполнения программы):

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)
}
15 голосов
/ 19 июля 2015

Сотни тысяч на Go Часто задаваемые вопросы: Почему вместо потоков используются программы? :

Практично создавать сотни тысяч программ в одном и том же адресном пространстве.

Тест test / chan / goroutines.go создает 10000 и может легко сделать больше, но предназначен для быстрой работы;Вы можете изменить номер в вашей системе, чтобы экспериментировать.Вы можете легко запустить миллионы при наличии достаточного объема памяти, например на сервере.

Чтобы понять максимальное количество процедур, обратите внимание, что стоимость каждой процедуры - это, прежде всего, стек.Снова в разделе часто задаваемых вопросов:

… подпрограммы могут быть очень дешевыми: они имеют небольшие накладные расходы помимо стека, занимающего всего несколько килобайт.

A назадпри вычислении конверта предполагается, что для каждой подпрограммы выделена одна 4 КиБ страница , выделенная для стека (4 КиБ - довольно равномерный размер), плюс небольшие накладные расходы для блока управления (например, Блок управления потоком ) для времени выполнения;это согласуется с тем, что вы наблюдали (в 2011 году, pre-Go 1.0).Таким образом, подпрограммы на 100 Ки занимают около 400 МБ памяти, а подпрограммы на 1 Ми занимают около 4 ГБ памяти, которые по-прежнему управляемы на настольном компьютере, немного для телефона и очень управляемы на сервере.На практике размер стартового стека варьируется от половины страницы (2 КиБ) до двух страниц (8 КиБ), поэтому это примерно правильно.

Размер стартового стека со временем менялся;он начинался с 4 КиБ (одна страница), затем в 1,2 был увеличен до 8 КиБ (2 страницы), затем в 1,4 был уменьшен до 2 КиБ (половина страницы).Эти изменения произошли из-за сегментированных стеков, вызывающих проблемы с производительностью при быстром переключении между сегментами («горячее разделение стеков»), поэтому они были увеличены, чтобы смягчить (1.2), а затем уменьшились, когда сегментированные стеки были заменены смежными стеками (1.4):

Go 1.2 Примечания к выпуску: Размер стека :

В Go 1.2 минимальный размер стека при создании программы был увеличен с 4 КБ до 8 КБ

Go 1.4 Примечания к выпуску: Изменения во время выполнения :

начальный размер по умолчанию для стека goroutine в 1.4 был уменьшен с 8192 байтов до2048 байт.

Память для каждой программы в основном стековая, и она начинается с низкого уровня и растет, так что вы можете дешево иметь много процедур.Вы могли бы использовать меньший начальный стек, но тогда он должен был бы расти быстрее (выиграть пространство за счет затрат времени), и преимущества уменьшились бы из-за того, что блок управления не сжимался.Возможно исключить стек, по крайней мере, при его замене (например, сделать все выделения в куче или сохранить стек в куче при переключении контекста), хотя это снижает производительность и увеличивает сложность.Это возможно (как в Erlang) и означает, что вам просто понадобится блок управления и сохраненный контекст, позволяющий еще один коэффициент в 5 × –10 × по числу процедур, который теперь ограничен размером блока управления и размером подпрограммы в куче.локальные переменныеТем не менее, это не очень полезно, если только вам не нужны миллионы крошечных спящих программ.

Поскольку основное использование множества программ предназначено для задач, связанных с вводом-выводом (конкретно для обработки блокирующих системных вызовов, особенно сети или файловой системы).IO), у вас гораздо больше шансов столкнуться с ограничениями ОС для других ресурсов, а именно сетевых сокетов или файловых дескрипторов: golang-nuts ›Максимальное количество процедур и дескрипторов файлов? .Обычный способ решения этой проблемы - с помощью пула дефицитного ресурса или, проще, просто ограничив число с помощью семафора ;см. Сохранение файловых дескрипторов в Go и Ограничение параллелизма в Go .

5 голосов
/ 14 декабря 2011

Перефразируя, есть ложь, проклятая ложь и ориентиры. Как признался автор теста Erlang,

Само собой разумеется, что в машина на самом деле делает что нибудь полезное. стресс-тестирование Эрланг

Какая у вас аппаратура, какая у вас операционная система, где находится исходный код вашего теста? Что тест пытается измерить и доказать / опровергнуть?

5 голосов
/ 14 декабря 2011

Это полностью зависит от системы, на которой вы работаете. Но горутины очень легкие. У среднего процесса не должно быть проблем с 100 000 одновременных подпрограмм. Разве это не касается вашей целевой платформы, конечно, мы не можем ответить, не зная, что это за платформа.

2 голосов
/ 28 апреля 2014

Вот отличная статья Дэйва Чейни на эту тему: http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite

0 голосов
/ 29 января 2015

Если количество горутинов когда-либо становится проблемой, вы легко можете ограничить его для своей программы:
См. mr51m0n / gorc и этот пример .

Установить пороговые значения для числа запущенных программ

Может увеличивать и уменьшать счетчик при запуске или остановке программы.
Он может ожидать минимального или максимального числа запущенных программ, что позволяет установить пороговые значения для количества одновременно запускаемых управляемых gorc.

...