Una gorutina es un hilo ligero de ejecución, gestionado por el runtime de Go. Para iniciar una nueva gorutina, se utiliza la palabra clave go antes de la llamada a la función. Internamente, se crea una estructura que describe el stack y el estado de la tarea, que se añade a la cola del planificador de gorutinas.
A diferencia de los hilos del sistema operativo, una gorutina tiene un stack inicial mucho menor (normalmente 2 KB), y el stack se amplía automáticamente según sea necesario. El planificador de Go asigna automáticamente gorutinas a los hilos disponibles del sistema operativo (modelo M:N).
Las principales diferencias con los hilos (threads):
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d comenzó trabajo %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d terminó trabajo %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { <-results } }
¿Pueden las gorutinas de Go ejecutarse en paralelo en múltiples núcleos?
A menudo se recibe la respuesta incorrecta: "No, porque Go utiliza hilos verdes". En realidad, mediante una variable de entorno o la llamada runtime.GOMAXPROCS(n), Go puede paralelizar la ejecución de gorutinas en todos los núcleos disponibles del procesador.
import "runtime" func main() { runtime.GOMAXPROCS(4) // Permite usar 4 núcleos ... }
Historia
En un proyecto de servicio backend en Go, se implementó un pool de trabajadores a través de gorutinas, pero los programadores olvidaron limitar la cantidad de gorutinas que podían ejecutarse simultáneamente. Como resultado, al aumentar la carga, la aplicación lanzaba miles de gorutinas, lo que llevó al agotamiento de la memoria y al colapso del servicio. El problema se resolvió imponiendo un límite a las gorutinas activas (por ejemplo, mediante un semáforo o un pool de trabajadores).
Historia
Uno de los empleados sincronizó incorrectamente los datos entre gorutinas, utilizando variables globales normales sin mutexes o canales. Esto provocó una condición de carrera (race condition), lo que causó que se produjeran errores periódicamente durante el procesamiento de pagos. El problema solo se detectó después de ejecutarse en producción.
Historia
En el servicio de parsing se pasó por alto el momento de pasar canales nil en select: después de cerrar el canal, select continuó esperando datos de manera bloqueante, y parte de las gorutinas "se colgaron". Se corrigió asignando nil al canal cerrado y manejando correctamente el select.