Concurrencia en Golang

¿Qué es la concurrencia?
La concurrencia es la capacidad de un programa para ejecutar múltiples tareas al mismo tiempo (o al menos dar la ilusión de hacerlo). Es especialmente útil cuando queremos realizar operaciones independientes de forma simultánea, como servir múltiples usuarios en un servidor web, procesar archivos o consumir APIs externas.
“Concurrencia no es paralelismo. Concurrencia es estructurar cosas para que se vean como si fueran simultáneas; paralelismo es hacer muchas cosas a la vez.” — Rob Pike (co-creador de Go)
Go fue diseñado desde cero para manejar concurrencia de manera simple, segura y eficiente. Por eso tiene herramientas integradas como goroutines
y channels
que facilitan su uso.
¿Qué son las goroutines?
Una goroutine
es una función que se ejecuta de forma concurrente con otras funciones. Se crean usando la palabra clave go
antes de llamar a la función:
go fmt.Println("Hola desde una goroutine")
Cada vez que usás go
, lanzás una nueva goroutine. Son extremadamente livianas y pueden escalar a miles sin consumir demasiados recursos, a diferencia de los hilos tradicionales (threads).
Comunicación entre goroutines: channels
Go propone un enfoque donde "no compartas memoria para comunicarte, comunicáte para compartir memoria". Esto se logra con los channels, que permiten enviar y recibir datos entre goroutines de forma segura.
Creación y uso básico:
ch := make(chan string)
// enviar mensaje
ch <- "mensaje"
// recibir mensaje
dato := <-ch
También podés usar funciones anónimas o normales para lanzar goroutines que usen el canal:
ch := make(chan string)
go func() {
ch <- "Hola desde goroutine"
}()
fmt.Println(<-ch)
Ejemplo práctico: Simulador de descargas
package main
import (
"fmt"
"math/rand"
"time"
)
func descargar(nombre string, ch chan string) {
tiempo := rand.Intn(5) + 1
time.Sleep(time.Duration(tiempo) * time.Second)
ch <- fmt.Sprintf("%s descargado en %d segundos", nombre, tiempo)
}
func main() {
rand.Seed(time.Now().UnixNano())
archivos := []string{"foto.jpg", "video.mp4", "documento.pdf"}
ch := make(chan string)
for _, archivo := range archivos {
go descargar(archivo, ch)
}
for i := 0; i < len(archivos); i++ {
fmt.Println(<-ch)
}
}
Este ejemplo lanza varias descargas en paralelo y recibe el resultado a medida que cada una termina.
WaitGroup y select
sync.WaitGroup
Permite esperar a que un conjunto de goroutines termine su trabajo.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Trabajo terminado")
}()
wg.Wait()
select
Permite escuchar múltiples canales al mismo tiempo:
select {
case msg1 := <-ch1:
fmt.Println("Mensaje 1:", msg1)
case msg2 := <-ch2:
fmt.Println("Mensaje 2:", msg2)
}
Ejemplo con una API y WaitGroup
Veamos un ejemplo práctico donde usamos sync.WaitGroup para hacer múltiples requests concurrentes a la API de usuarios de JSONPlaceholder.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email"`
}
func obtenerUsuario(id int, wg *sync.WaitGroup) {
defer wg.Done()
url := fmt.Sprintf("https://jsonplaceholder.typicode.com/users/%d", id)
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error en request:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var user User
if err := json.Unmarshal(body, &user); err != nil {
fmt.Println("Error al parsear JSON:", err)
return
}
fmt.Printf("Usuario %d: %s (%s)\n", user.ID, user.Name, user.Email)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go obtenerUsuario(i, &wg)
}
wg.Wait()
fmt.Println("Todos los usuarios fueron obtenidos.")
}
Errores comunes a evitar
- No cerrar un canal si ya no se va a usar
- Deadlocks: dos goroutines esperando entre sí
- Goroutines que nunca terminan (fugas)
- Enviar o recibir fuera de sincronía
¿Cuándo usar concurrencia?
- Ejecutar tareas independientes al mismo tiempo (envío de emails, logs, etc.)
- Servir múltiples usuarios en una API REST
- Procesar eventos o tareas en segundo plano
- Scraping web o bots que consultan muchas fuentes
- Automatización de procesos pesados que se pueden dividir