threads golang close go multitasking goroutine

golang - goroutine pool



Número máximo de goroutines (6)

¿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 un poco el rendimiento de los goroutines y obtuve esos resultados:

  • Parece que la vida útil de la rutina 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


Cientos de miles, por preguntas frecuentes de Go: ¿Por qué goroutines en lugar de hilos? :

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

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

Para comprender el número máximo de goroutines, tenga en cuenta que el costo por goroutine es principalmente la pila. Por preguntas frecuentes de nuevo:

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

Un cálculo posterior es suponer que cada goroutine tiene una page 4 kib asignada para la pila (4 kib es un tamaño bastante uniforme), más una pequeña sobrecarga para un bloque de control (como un bloque de control de subprocesos ) para el tiempo de ejecución; esto concuerda con lo que observó (en 2011, pre-Go 1.0). Por lo tanto, las rutinas de 100 Ki tomarían alrededor de 400 MiB de memoria, y 1 Mi rutinas tomaría alrededor de 4 GiB de memoria, que aún es manejable en el escritorio, un poco más para un teléfono y muy manejable en un servidor. En la práctica, la pila inicial tiene un rango de tamaño de media página (2 KiB) a 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 KiB (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 apilamientos segmentados que causaron problemas de rendimiento al pasar y retroceder rápidamente entre segmentos ("división de pila caliente"), por lo que aumentaron para mitigar (1.2), luego disminuyeron cuando las pilas segmentadas fueron reemplazadas por pilas contiguas (1.4):

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

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

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

el tamaño de inicio predeterminado para una pila de goroutine en 1.4 se ha reducido de 8192 bytes a 2048 bytes.

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

Dado que el uso principal de tener muchos goroutines es para tareas ligadas a IO (concretamente para procesar syscalls de bloqueo, en particular red o sistema de archivos IO), es mucho más probable que se tope con límites de OS en otros recursos, concretamente sockets de red o manejadores de archivos : golang-nuts> ¿El número máximo de goroutines y descriptores de archivos? . La forma habitual de abordar esto es con un pool del recurso escaso, o simplemente limitando el número a través de un semaphore ; consulte Conservar descriptores de archivo en Go y limitar la concurrencia en Go .


Eso depende completamente del sistema en el que se está ejecutando. Pero los goroutines son muy livianos. Un proceso promedio no debería tener problemas con 100.000 rutinas concurrentes. Si esto se aplica a su plataforma objetivo es, por supuesto, algo a lo que no podemos responder sin saber cuál es esa plataforma.


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

Ni que decir tiene que no había suficiente memoria en la máquina para hacer algo realmente útil. stress-testing erlang

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


Si la cantidad de goroutine alguna vez se convierte en un problema, puede limitarlo fácilmente a su programa:
Ver mr51m0n/gorc y este ejemplo .

Establecer umbrales sobre el número de goroutines corriendo

Puede aumentar y disminuir un contador al iniciar o detener un goroutine.
Puede esperar que se ejecute un número mínimo o máximo de goroutines, lo que permite establecer umbrales para el número de gorc gobernados por gorc que se ejecutan al mismo tiempo.


Si un goroutine está bloqueado, no hay costo involucrado más que:

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

Los costos (en términos de memoria y tiempo promedio para realmente comenzar a ejecutar un 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 poco menos de 1 millón.

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

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) }