En 2015 escribí un artículo sobre qué me gustaba de Go, y una de las cosas que más me costaba explicar entonces era la concurrencia. Las goroutines parecían threads, pero no lo eran. Se ejecutaban “de forma independiente”, pero no necesariamente en paralelo. Todo sonaba un poco raro hasta que te ponías a escribir código.
Once años después, Anton Zhiyanov ha publicado justo el recurso que me habría gustado tener aquella tarde: Go Concurrency Distilled, un minilibro gratuito que repasa toda la concurrencia de Go en una sola página, con ejemplos interactivos que puedes modificar y ejecutar en el navegador.
Qué es (y qué no es)
El propio autor lo deja claro desde el principio: es un repaso rápido, no una guía para principiantes. Si quieres aprender concurrencia desde cero con ejercicios, remite a su otro libro, Gist of Go: Concurrency. Este es más bien la chuleta que tienes abierta en una pestaña cuando llevas un tiempo sin tocar select y no recuerdas qué pasa exactamente al leer de un canal cerrado.
Y lo cubre prácticamente todo:
Goroutines · Canales · Select · Pipelines · Time · Context · Wait groups · Data races · Race conditions · Mutexes · Semáforos · Señalización · Once · Pools · Atomics · Testing · Scheduling · Diagnóstico
Cada tema va al grano: una explicación de un par de párrafos, un ejemplo que puedes ejecutar y, cuando hace falta, la trampa en la que suele caer todo el mundo.
Un detalle que me ha gustado: el libro se presenta como AI-free, escrito sin ayuda de IA. En un momento en el que cuesta distinguir qué documentación está escrita por alguien que sabe del tema, es una declaración de intenciones.
Lo que más me ha gustado
Está al día
Usa las APIs modernas de Go, no las de hace cinco años. Por ejemplo, WaitGroup.Go, que combina el Add, el lanzamiento de la goroutine y el Done en una sola llamada:
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
fmt.Print(".")
})
}
wg.Wait()
Si has escrito Go durante años, reconocerás lo que desaparece: el wg.Add(1) antes de cada goroutine y el defer wg.Done() dentro, que era fácil olvidar. Encaja muy bien con lo que contaba hace un par de días sobre go fix y la modernización del código: el lenguaje sigue evolucionando, y conviene que los recursos de aprendizaje lo hagan también.
También explica synctest, el paquete para probar código concurrente con un reloj falso y sin time.Sleep repartidos por los tests. Es de esas cosas que, cuando la descubres, te preguntas cómo has vivido sin ella.
Distingue data race de race condition
Es una distinción que mucha gente mezcla, y el libro la explica con un ejemplo muy claro.
Una data race ocurre cuando varias goroutines acceden a los mismos datos y al menos una los modifica. Go tiene una herramienta para detectarlas: go run -race.
Una race condition es otra cosa: las operaciones individuales pueden ser seguras, pero el orden en que ocurren deja el sistema en un estado incorrecto. El ejemplo del libro es un saldo de 50 y dos retiradas de 40 en paralelo: las dos comprueban que hay saldo suficiente antes de que ninguna descuente, y el saldo acaba en -30.
Lo interesante es que el detector de carreras no encuentra nada en ese caso, porque técnicamente no hay data race. La solución pasa por proteger la operación completa (comprobar y descontar) con un mutex. Es un buen recordatorio de que -race ayuda muchísimo, pero no piensa por ti.
Los patrones de siempre, bien explicados
La sección de pipelines es probablemente la más útil para el día a día: canales de salida, canal done para avisar de que se ha terminado, canal de cancelación y tres formas de gestionar errores en un pipeline concurrente (parar en el primer error, devolver un tipo Result con el valor y el error, o recoger los errores por separado). Son patrones que acabas reinventando si no los conoces, y verlos juntos ayuda a elegir el adecuado.
Lo mismo con los primitivos menos conocidos: el semáforo implementado con un canal con buffer, el rendezvous y la barrera hechos con un WaitGroup, o sync.Cond para señalizar eventos entre goroutines.
Lo que me llevo
Go sigue teniendo lo que me gustó en 2015: un modelo de concurrencia que, una vez entiendes, resulta sorprendentemente sencillo. Pero “sencillo” no significa “sin trampas”. Un canal nil que bloquea para siempre, un canal que se cierra dos veces y provoca un panic, un timer que se crea de nuevo en cada vuelta de un bucle en lugar de reutilizarse… El libro las recoge casi todas, y tenerlas en un solo sitio tiene mucho valor.
Es de esos enlaces que conviene tener a mano y pasar a cualquiera que empiece a trabajar en serio con Go. Si ya conoces el lenguaje, léelo de principio a fin una vez: seguro que encuentras un par de cosas que dabas por sabidas y no lo estaban tanto.

