Número máximo de goroutines


¿cuántos goroutines puedo usar sin dolor? Por ejemplo, Wikipedia dice que en Erlang se pueden crear 20 millones de procesos sin degradar el rendimiento.

Actualización: Acabo de investigar en el rendimiento de goroutines un poco y obtuve tales resultados:

  • Parece que la vida de goroutine es más que calcular sqrt() 1000 veces (~45µs para mí), la única limitación es la memoria
  • Goroutine cuesta 4-4.5 KB
Author: Flimzy, 2011-12-14

6 answers

Si se bloquea una goroutina, no hay ningún coste que no sea:

  • uso de memoria
  • recolección de basura más lenta

Los costos (en términos de memoria y tiempo promedio para comenzar a ejecutar una goroutine) son:

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

En una máquina con 4 GB de memoria instalada, esto limita el número máximo de goroutines a un poco menos de 1 millón.


Código fuente (no es necesario leer esto si ya entiende los números impresos above):

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)
}
 39
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-09 06:49:03

Cientos de miles, per Go FAQ: ¿Por qué goroutines en lugar de hilos?:

Es práctico crear cientos de miles de goroutines en el mismo espacio de direcciones.

La prueba prueba/chan/goroutines.go crea 10,000 y podría hacer más fácilmente, pero está diseñado para ejecutarse rápidamente; puede cambiar el número en su sistema para experimentar. Puede ejecutar fácilmente millones, con suficiente memoria, como en un servidor.

Para entender el número máximo de goroutines, tenga en cuenta que el costo por goroutine es principalmente la pila. Por FAQ otra vez:

Gor goroutines, puede ser muy barato: tienen poca sobrecarga más allá de la memoria para la pila, que es solo unos pocos kilobytes.

Un cálculo de back-of-the-envelop es asumir que cada goroutine tiene una página de 4 KiB asignada para la pila (4 KiB es un tamaño bastante uniforme), además de una pequeña sobrecarga para un bloque de control (como un Bloque de Control de Hilo ) para tiempo de ejecución; esto concuerda con lo observado (en 2011, pre-Go 1.0). Por lo tanto, las rutinas de 100 Ki tomarían aproximadamente 400 MiB de memoria, y las rutinas de 1 Mi tomarían aproximadamente 4 GiB de memoria, que todavía es manejable en el escritorio, un poco demasiado para un teléfono y muy manejable en un servidor. En la práctica, la pila inicial ha variado en tamaño desde media página (2 KiB) hasta dos páginas (8 KiB), por lo que esto es aproximadamente correcto.

El tamaño de la pila inicial ha cambiado con el tiempo; comenzó en 4 kb( una página), luego en 1.2 se aumentó a 8 KiB (2 páginas), luego en 1.4 se redujo a 2 KiB (media página). Estos cambios se debieron a que las pilas segmentadas causaban problemas de rendimiento al cambiar rápidamente entre segmentos ("hot stack split"), por lo que aumentaron para mitigar (1.2), y luego disminuyeron cuando las pilas segmentadas se reemplazaron con pilas contiguas (1.4):

Go 1.2 Notas de la versión: Tamaño de la pila :

En Go 1.2, el tamaño mínimo de la pila cuando se crea una goroutine se ha elevado de 4KB a 8KB

Go 1.4 Notas de la versión: Cambios en el tiempo de ejecución :

El tamaño inicial predeterminado para la pila de un goroutine en 1.4 se ha reducido de 8192 bytes a 2048 bytes.

La memoria Per-goroutine es en gran parte apilada, y comienza baja y crece para que pueda tener muchos goroutines a bajo costo. Podría usar una pila inicial más pequeña, pero entonces tendría que crecer antes( ganar espacio a costo de tiempo), y los beneficios disminuyen debido a el bloque de control no se encoge. Es posible eliminar la pila, al menos cuando se intercambia (por ejemplo, hacer toda la asignación en el montón, o guardar la pila en el montón en el cambio de contexto), aunque esto perjudica el rendimiento y agrega complejidad. Esto es posible (como en Erlang), y significa que solo necesitaría el bloque de control y el contexto guardado, lo que permite otro factor de 5×-10× en número de goroutines, limitado ahora por el tamaño del bloque de control y el tamaño en el montón de las variables goroutine-local. Sin embargo, esto no es terriblemente útil, a menos que necesites millones de pequeños goroutines dormidos.

Dado que el uso principal de tener muchas goroutinas es para tareas vinculadas a IO (concretamente para procesar llamadas de sistema de bloqueo, notablemente IO de red o sistema de archivos), es mucho más probable que se encuentre con límites de SO en otros recursos, a saber, sockets de red o manejadores de archivos: golang-nuts " El número máximo de goroutinas y descriptores de archivos?. La forma habitual de abordar esto es con un pool del recurso escaso, o más simplemente por limitando el número a través de un semáforo ; véase Conservando Descriptores de archivo en Go y Limitando la Concurrencia en Go.

 14
Author: Nils von Barth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-09 04:39:38

Eso depende completamente del sistema en el que se esté ejecutando. Pero los goroutines son muy ligeros. Un proceso promedio no debería tener problemas con 100.000 rutinas concurrentes. Si esto va para su plataforma de destino es, por supuesto, algo que no podemos responder sin saber lo que es esa plataforma.

 5
Author: jimt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-14 19:01:09

Para parafrasear, hay mentiras, malditas mentiras, y puntos de referencia. Como confesó el autor del punto de referencia de Erlang,

No hace falta decir que no había suficiente memoria en el máquina para hacer realmente cualquier cosa útil. pruebas de resistencia erlang

¿Cuál es su hardware, cuál es su sistema operativo, dónde está su código fuente de benchmark? ¿Cuál es el punto de referencia tratando de medir y probar / refutar?

 4
Author: peterSO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-14 19:04:56

Aquí hay un gran artículo de Dave Cheney sobre este tema: http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite

 2
Author: Travis Reeder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-28 17:08:21

Si el número de goroutine alguna vez se convierte en un problema, puede limitarlo fácilmente para su programa:
Ver mr51m0n/gorc y este ejemplo.

Establecer umbrales en número de goroutines

Puede aumentar y disminuir de un contador al iniciar o detener una goroutine.
Puede esperar un número mínimo o máximo de goroutinas en ejecución, lo que permite establecer umbrales para el número de gorc goroutinas gobernadas que se ejecutan al mismo tiempo tiempo.

 0
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-29 20:53:36