Concurrencia y multihilo en Go: nivel avanzado

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

Concurrencia y multihilo en Go: nivel avanzado



Go (Golang) fue diseñado desde el principio como un lenguaje para escribir aplicaciones escalables y de alto rendimiento. En su base se encuentra un potente modelo de concurrencia, basado en gorutinas y canales. Si ya dominas los ejemplos básicos con go func() y canales simples, es hora de pasar al siguiente nivel. En este artículo, analizaremos patrones avanzados de trabajo con concurrencia en Go que te ayudarán a escribir código confiable, eficiente y seguro.



1. Gestión del ciclo de vida de las gorutinas: Context y WaitGroup



Uno de los problemas más comunes es la fuga de gorutinas. Una gorutina que espera infinitamente datos de un canal nunca finalizará si el canal se cierra incorrectamente. Para gestionar el ciclo de vida de las gorutinas en Go existen dos herramientas principales: sync.WaitGroup y context.Context.



sync.WaitGroup para esperar la finalización

Usa sync.WaitGroup cuando necesites esperar a que finalice un conjunto fijo de gorutinas. Es un mecanismo simple y eficaz.



package main

import (\t"fmt"\t"sync"\t"time")

func worker(id int, wg *sync.WaitGroup) {\tdefer wg.Done() // Disminuimos el contador al salir\tfmt.Printf("Worker %d comenzó a trabajar", id)\ttime.Sleep(time.Second)\tfmt.Printf("Worker %d terminó de trabajar", id)}

func main() {\tvar wg sync.WaitGroup

\tfor i := 1; i <= 3; i++ {\t\twg.Add(1) // Aumentamos el contador antes de iniciar\t\tgo worker(i, &wg)\t}

\twg.Wait() // Nos bloqueamos hasta que el contador sea 0\tfmt.Println("Todos los workers terminaron")}


context.Context para cancelación y plazos

Cuando una gorutina trabaja en un bucle o realiza una operación larga, necesita poder finalizar correctamente ante una señal externa. context.Context es la forma estándar de transmitir una señal de cancelación, plazo o tiempo de espera.



package main

import (\t"context"\t"fmt"\t"time")

func longRunningTask(ctx context.Context) {\tfor {\t\tselect {\t\tcase <-ctx.Done():\t\t\tfmt.Println("La gorutina recibió señal de parada")\t\t\treturn\t\tdefault:\t\t\ttime.Sleep(500 * time.Millisecond)\t\t\tfmt.Println("Trabajando...")\t\t}\t}}

func main() {\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\tdefer cancel() // Es importante llamar a cancel para liberar recursos

\tgo longRunningTask(ctx)

\ttime.Sleep(3 * time.Second)\tfmt.Println("Main finalizó")}


2. Canales: buffering, cierre y patrón Fan-Out/Fan-In



Los canales son la forma principal de sincronización y transferencia de datos entre gorutinas. A nivel avanzado, es importante entender la diferencia entre canales con buffer y sin buffer, cerrarlos correctamente y usar patrones de agregación.



Con buffer vs Sin buffer

Un canal sin buffer (sin búfer) bloquea al emisor hasta que el receptor lea los datos. Un canal con buffer permite enviar varios mensajes sin esperar, hasta que el búfer se llene. La elección depende de la tarea: para sincronización estricta usa canales sin buffer, para suavizar la carga usa canales con buffer.



Patrón Fan-Out/Fan-In

Este es uno de los patrones más potentes. Fan-Out: distribución de tareas entre varias gorutinas (workers). Fan-In: recolección de resultados de varias gorutinas en un solo canal.



package main

import (\t"fmt"\t"sync")

func producer(nums ...int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor _, n := range nums {\t\t\tout <- n\t\t}\t\tclose(out)\t}()\treturn out}

func square(in <-chan int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor n := range in {\t\t\tout <- n * n\t\t}\t\tclose(out)\t}()\treturn out}

func merge(channels ...<-chan int) <-chan int {\t

Blogs

Book Recommendations