> serie: aprende go de 0 a 100 | artículo 12 de 19
En el artículo anterior las goroutines volaban cada una a lo suyo y las esperábamos con time.Sleep, que es como sincronizar relojes gritando por la ventana. Hoy llega la herramienta de verdad: los channels en Go, las tuberías tipadas por las que las goroutines se pasan datos y, de regalo, se sincronizan.
Channels: enviar, recibir, bloquear
Un channel se crea con make y tiene tipo: por un chan string solo viajan strings (A Tour of Go, channels):
ch := make(chan string)
go func() {
ch <- "hola desde la goroutine" // enviar
}()
msg := <-ch // recibir: BLOQUEA hasta que llegue algo
fmt.Println(msg)
// Sin buffer, envío y recepción se esperan mutuamente:
// es una cita, no un buzón.
La flecha <- lo es todo: hacia el channel envía, desde el channel recibe. Y el detalle que lo cambia todo: en un channel sin buffer ambas operaciones bloquean hasta que la otra parte aparece. Esa espera mutua ES la sincronización: cuando la recepción termina, sabes que el envío ocurrió. Nada de sleep a ojo.
Buffer, close y range
Con capacidad, el channel pasa de cita a buzón (A Tour of Go, buffered channels, range and close y especificación, close):
// Con buffer: el envío solo bloquea si está lleno.
buzon := make(chan int, 3)
buzon <- 1
buzon <- 2 // nadie recibe y no pasa nada, hay hueco
// close avisa de que no habrá más envíos.
close(buzon)
// range consume hasta que el channel se cierra y vacía:
for v := range buzon {
fmt.Println(v)
}
// La coma ok también vive aquí:
v, ok := <-buzon // ok es false: cerrado y vacío
Reglas de la casa que evitan panics: quien cierra es siempre el EMISOR, nunca el receptor. Enviar a un channel cerrado es panic. Cerrar dos veces, también. Y recibir de uno cerrado devuelve zero values con ok en falso, que es justo lo que range usa para saber cuándo parar.
Las firmas pueden declarar dirección, y el compilador la hace cumplir:
// Direcciones en la firma: el compilador vigila.
func produce(salida chan<- int) { // solo enviar
salida <- 42
}
func consume(entrada <-chan int) { // solo recibir
fmt.Println(<-entrada)
}
select: escuchar varios channels a la vez
El select es un switch de operaciones de channel: espera hasta que alguno de sus cases pueda proceder y, si hay varios listos, elige uno al azar (A Tour of Go, select y especificación):
select {
case msg := <-ch1:
fmt.Println("llegó por ch1:", msg)
case msg := <-ch2:
fmt.Println("llegó por ch2:", msg)
case <-time.After(2 * time.Second):
fmt.Println("timeout: nadie contestó")
}
// Con default deja de bloquear:
select {
case msg := <-ch1:
fmt.Println(msg)
default:
fmt.Println("nada pendiente, sigo con mi vida")
}
Los dos patrones del ejemplo son fondo de armario: timeout con time.After para no esperar eternamente y default para consultar sin bloquear. Si trabajaste con Promise.race en JS, la idea te suena, pero esto funciona con cualquier número de operaciones en ambas direcciones.

[select es la centralita: atiende la primera línea que se libera.]
La fontanería completa
Con los channels en Go y select ya puedes montar el eslogan del artículo anterior: en vez de compartir el contador y protegerlo, una sola goroutine es dueña del dato y las demás le mandan mensajes (Effective Go, channels). El contador con carrera del reto pasado se arregla así: una goroutine suma lo que le llega por un channel y nadie más toca la variable.
Reto de la semana: pon a prueba los channels en Go
- Ping pong: dos goroutines y un channel. Una envía «ping», la otra responde «pong», diez rondas y a casa.
- Arregla el contador del artículo 11 con un channel: 1000 goroutines envían 1 y una acumuladora suma. Verifica con -race que la carrera desapareció.
- Simula una consulta lenta con time.Sleep aleatorio y ponle un timeout de un segundo con select.
- Bonus: productor que envía cinco números y cierra. Consumidor con range. Añade un segundo productor y descubre por qué cerrar se complica con varios emisores (pista para el artículo 14).
Channels en Go: nivel completado.
En el próximo artículo: patrones de concurrencia. Worker pools, fan-in, fan-out y pipelines, los planos de fontanería profesional.
> exit