Go fue diseñado originalmente para escribir programas de alto rendimiento en red y paralelos, por lo que se presta especial atención al programador (scheduler) integrado y a la gestión sencilla de la paralelización mediante gorutinas. A diferencia de la mayoría de los lenguajes, donde los hilos del sistema operativo son gestionados directamente por el usuario del lenguaje, en Go las gorutinas son más livianas, y miles de ellas pueden operar simultáneamente sobre un número fijo de hilos del sistema.
Problema: el control directo de hilos (threads) es complicado: esto lleva a un rápido uso de recursos, data races y dificultades en la gestión de la memoria.
Solución: Go utiliza un modelo M:N — un gran número de gorutinas (M) se multiplexan en una cantidad limitada de hilos del sistema operativo (N). Esto es gestionado por el programador, implementado en el nivel de tiempo de ejecución de Go, que automáticamente balancea y redistribuye la ejecución de las gorutinas. El programador solo gestiona el inicio y la sincronización de las gorutinas, no los hilos del sistema operativo directamente.
Ejemplo de código:
package main import ( "fmt" "time" ) func worker(id int) { fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { for i := 0; i < 5; i++ { go worker(i) } time.Sleep(2 * time.Second) }
Características clave:
¿Cada gorutina se ejecutará de manera concurrente en un núcleo de procesador separado?
No. Las gorutinas se multiplexan y el programador determina el número real de tareas ejecutadas concurrentemente.
¿Se puede gestionar manualmente la ejecución de gorutinas/hilos específicos?
No. El tiempo de ejecución de Go no proporciona una interfaz para la planificación directa. La excepción es GOMAXPROCS para establecer el número de hilos del sistema operativo.
¿Significa un gran número de gorutinas una aceleración automática del programa?
No. Un gran número de operaciones concurrentes puede llevar a sobrecargas adicionales: cambios de contexto, competencia por recursos, aumento del tiempo de GC y consumo de memoria.
Un microservicio procesaba solicitudes entrantes de manera paralela, no limitaba el número de gorutinas y no esperaba la finalización a través de WaitGroup: resultado: aumento en el tiempo de respuesta, data races, timeouts impredecibles.
Pros:
Contras:
El administrador de trabajadores se implementó a través de un grupo de gorutinas, limitando la cantidad de tareas que trabajan simultáneamente a través de un semáforo o canales. WaitGroup espera correctamente la finalización de todas las tareas.
Pros:
Contras: