Programación asíncrona en Go: guía completa para principiantes

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Programación asíncrona en Go: guía completa para principiantes

Go (Golang) fue creado teniendo en cuenta las necesidades del desarrollo moderno, donde el rendimiento y el paralelismo juegan un papel clave. A diferencia de muchos lenguajes, donde la asincronía se implementa mediante callbacks o promesas, Go ofrece mecanismos integrados y elegantes: gorutinas y canales. Esta guía te presentará los fundamentos de la programación asíncrona en Go y te mostrará cómo escribir código concurrente eficiente.



¿Qué son las gorutinas y en qué se diferencian de los hilos?

Una gorutina es un hilo de ejecución ligero, gestionado por el runtime de Go. A diferencia de los hilos del sistema operativo, las gorutinas consumen muchos menos recursos: su pila comienza con unos pocos kilobytes y puede crecer y reducirse dinámicamente. Esto permite ejecutar millones de gorutinas simultáneamente sin una carga significativa en la memoria.

Iniciar una gorutina es muy sencillo: basta con añadir la palabra clave go antes de la llamada a la función:

package main

import ( "fmt" "time")

func sayHello() { fmt.Println("¡Hola desde una gorutina!")}

func main() { go sayHello() // Inicio de la gorutina time.Sleep(100 * time.Millisecond) // Esperamos para que la gorutina tenga tiempo de ejecutarse fmt.Println("Función principal finalizada")}

Es importante destacar: si la función principal main() finaliza, todas las gorutinas iniciadas serán detenidas forzosamente. Para la sincronización utilizamos herramientas especiales.



Sincronización con WaitGroup

La forma más sencilla de esperar a que finalicen las gorutinas es usar sync.WaitGroup. Funciona como un contador: lo incrementamos al iniciar una gorutina y lo decrementamos al finalizar. La función principal se bloquea hasta que el contador llega a cero.

package main

import ( "fmt" "sync" "time")

func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // Decrementamos el contador al finalizar fmt.Printf("Trabajador %d comenzó a trabajar", id) time.Sleep(time.Second) fmt.Printf("Trabajador %d terminó de trabajar", id)}

func main() { var wg sync.WaitGroup

for i := 1; i <= 3; i++ { wg.Add(1) // Incrementamos el contador antes de iniciar go worker(i, &wg) }

wg.Wait() // Esperamos a que todas las gorutinas finalicen fmt.Println("Todos los trabajadores han terminado su trabajo")}

Observa: wg.Add(1) se llama antes de iniciar la gorutina, y wg.Done() dentro de ella (normalmente mediante defer). Esto garantiza que el contador se decremente correctamente incluso en caso de pánico.



Canales: comunicación entre gorutinas

Los canales (channels) son «tuberías» tipificadas a través de las cuales las gorutinas pueden intercambiar datos de forma segura. Los canales proporcionan sincronización por defecto: el envío a un canal bloquea la gorutina hasta que otra gorutina lea el valor, y viceversa.

Los canales se crean con la función make:

package main

import "fmt"

func main() { // Creamos un canal no buferizado para números enteros ch := make(chan int)

// Iniciamos una gorutina que envía datos go func() { ch <- 42 // Enviamos un valor al canal }()

// Leemos del canal (se bloquea hasta que llegue un valor) value := <-ch fmt.Println("Valor recibido:", value)}

Los canales pueden ser:

  • No buferizados — síncronos, el envío se bloquea hasta la recepción.
  • Buferizados — asíncronos, el envío se bloquea solo cuando el búfer está lleno.
// Creación de un canal buferizado de 3 elementosbufferedCh := make(chan string, 3)bufferedCh <- "A"bufferedCh <- "B"bufferedCh <- "C" // Mientras no esté lleno, no bloquea


Blogs

Book Recommendations