Iteración con range y el ciclo de vida de los channels

for v := range ch es la forma idiomática en Go de consumir todos los valores de un channel hasta que este se cierra. Cuando ejecutas este bucle, la goroutine se bloquea en cada iteración esperando un nuevo valor; si el channel está vacío pero aún está abierto, la goroutine queda suspendida. Si el channel se cierra, el bucle termina automáticamente y la ejecución continúa con la siguiente línea después del bloque range.

Internamente, el range sobre un channel es una forma sintáctica de implementar un bucle infinito que utiliza la coma-ok idiomática para detectar el cierre. En lugar de escribir manualmente v, ok := <-ch y verificar si ok es false, el compilador lo hace por ti. El diseño de Go favorece esta estructura para simplificar el flujo de datos entre productores y consumidores.

Debes usar range cuando tengas un productor que sabe exactamente cuándo ha terminado de enviar datos y puede cerrar el canal de forma segura. Es la herramienta ideal para procesos de “streaming” o flujos de trabajo donde el ciclo de vida del canal es predecible.

Si intentas enviar un valor a un channel que ya ha sido cerrado, el programa lanzará un panic. Si intentas cerrar un channel que ya está cerrado, también ocurrirá un panic. Por el contrario, si un receptor intenta recibir de un channel cerrado, el programa no fallará; simplemente recibirá el valor por defecto del tipo de dato (como 0 para int o "" para string) y un segundo valor booleano que será false, permitiéndote saber que el canal ya no tiene más datos.

package main

import (
	"fmt"
	"time"
)

func main() {
	// jobs es el canal de datos (el flujo principal)
	jobs := make(chan int)
	// done es un "signal channel" para cancelación inmediata (patrón de terminación)
	done := make(chan struct{})

	// Goroutine productora: emite números y cierra el canal al terminar
	go func() {
		// El emisor (sender) es el único que debe cerrar el canal
		defer close(jobs) 
		for i := 1; i <= 3; i++ {
			time.Sleep(200 * time.Millisecond)
			fmt.Printf("Productor: enviando trabajo %d\n", i)
			jobs <- i
		}
		fmt.Println("Productor: he terminado, cerrando canal de trabajos")
	}()

	// Goroutine consumidora: usa range para procesar hasta que el canal se cierre
	go func() {
		// El range se detendrá automáticamente cuando se ejecute close(jobs)
		for j := range jobs {
			fmt.Printf("Consumidor: procesando %d\n", j)
			
			// Ejemplo de cómo se usa un canal 'done' para cancelar 
			// procesos largos sin esperar a que el canal de datos se cierre.
			select {
			case <-done:
				fmt.Println("Consumidor: señal de cancelación recibida, abortando")
				return
			default:
				// Continuar normalmente si no hay señal de cancelación
			}
		}
		fmt.Println("Consumidor: el canal se cerró, saliendo del loop de range")
	}()

	// Esperamos a que el flujo termine naturalmente
	time.Sleep(1 * time.Second)
	fmt.Println("Main: finalizando programa")
}

En el ejemplo anterior, la goroutine productora utiliza defer close(jobs) para asegurar que el canal se cierre una vez que el bucle de envío finaliza. Este cierre es la señal que el consumidor necesita para salir del for j := range jobs. Si el productor no ejecutara ese close, el consumidor se quedaría bloqueado para siempre en la última iteración del range, causando un goroutine leak.

El consumidor utiliza la sentencia range sobre jobs. Como mencionamos, esto es equivalente a verificar constantemente si el canal está abierto. Dentro del bucle, he añadido un select con un caso default. Este es el patrón de “done channel”: el select intenta leer de done. Si done no tiene nada (que es el caso normal), el default se ejecuta inmediatamente, permitiendo que el bucle continúe sin bloquearse. Si cerráramos done desde main, el consumidor detectaría la señal y saldría del loop incluso si jobs todavía tuviera datos pendientes.

El error frecuente

Un error clásico en sistemas distribuidos o con alta concurrencia es intentar enviar datos a un canal después de que este haya sido cerrado, generalmente porque la lógica de cierre es ambigua.

// Código que causa un panic
ch := make(chan int)
close(ch) // El canal se cierra
ch <- 10  // panic: send on closed channel

Esto sucede frecuentemente cuando tienes múltiples productores. Si un productor decide cerrar el canal porque él terminó, pero otros productores aún tienen datos por enviar, el programa colapsará. La regla de oro es: el emisor (sender) es el responsable de cerrar el canal, y solo debe hacerlo cuando esté seguro de que ningún otro emisor intentará enviar más datos.

135

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio