Introducción: la pregunta que atormenta a casi todos los principiantes en Go
Imagina: estás escribiendo tu primer programa serio en Go. Ya entendiste las gorutinas, dominaste los canales e incluso escribiste un par de ejemplos funcionales. Pero de repente te topas en Stack Overflow con una pregunta que ha acumulado miles de visitas: "Is it OK to leave a channel open?" (¿Está bien dejar un canal abierto?).
Spoiler: la respuesta no es tan clara como nos gustaría. Algunos gritan que un canal no cerrado es un pecado mortal y una fuga de recursos. Otros dicen que el recolector de basura de Go lo limpiará todo. ¿Dónde está la verdad? Analicémoslo con ejemplos reales, sin rodeos ni lecciones académicas.
Para quienes recién se familiarizan con el lenguaje — Go (o Golang) ofrece un elegante mecanismo de sincronización a través de canales. Pero como cualquier herramienta poderosa, los canales requieren un manejo cuidadoso. Especialmente cuando se trata de cerrarlos.
¿Por qué surgió esta pregunta? La raíz del problema
En Go hay un dicho: "No cierres el canal desde el lado del receptor — ciérralo desde el lado del emisor". Pero, ¿qué hacer si el programa termina y el canal queda abierto? La fuga de gorutinas es el mayor miedo. Cuando una gorutina se queda "colgada" esperando datos de un canal que nadie cerrará nunca, no finaliza. Y las gorutinas que no finalizan son una fuga de memoria y un desastre en producción.
Pero seamos honestos: en scripts pequeños o utilidades de prueba, esto no es grave. El recolector de basura de Go es inteligente, pero no limpia gorutinas — solo la memoria que ocupan. Por lo tanto, la pregunta de "cerrar o no" depende de la arquitectura de tu aplicación.
Ejemplo 1: cuando NO cerrar el canal está bien
Consideremos un escenario simple: el programa lee datos, los procesa y termina. Si el canal se usa solo dentro de main, y sabes con certeza que todas las gorutinas finalizarán — no es necesario cerrarlo.
package main
import "fmt"
func main() { ch := make(chan int)
// Gorutina emisora go func() { for i := 0; i < 5; i++ { ch <- i } // El canal NO se cierra }()
// Receptor for i := 0; i < 5; i++ { fmt.Println(<-ch) } // El programa finalizará sin errores fmt.Println("Listo, el canal quedó abierto, pero todo está bien")}¿Por qué funciona esto? Porque main finaliza y todas las gorutinas se destruyen junto con el proceso. El recolector de basura de Go limpiará la memoria. No hay fuga de gorutinas — el programa simplemente terminó.
Conclusión: si tu programa es un script que se ejecuta y muere, dejar un canal abierto es completamente normal. No provocará una fuga si controlas el ciclo de vida.
Ejemplo 2: cuando un canal no cerrado es una catástrofe
Ahora imagina un servidor que funciona 24/7. Tienes una gorutina que escucha el canal infinitamente:
package main
import ( "fmt" "time")
func worker(ch chan int) { // Lectura infinita for val := range ch { fmt.Println("Procesado:", val) } // Nunca llegaremos aquí si el canal no está cerrado fmt.Println("Gorutina finalizada")}
func main() { ch := make(chan int) go worker(ch)
// Enviamos datos ch <- 1 ch <- 2 ch <- 3
// Olvidamos cerrar el canal // close(ch)
time.Sleep(2 * time.Second) fmt.Println("El programa termina, ¡pero el worker sigue colgado!")}¿Qué sucederá? La gorutina worker quedará colgada en un bucle infinito, esperando nuevos datos. Nunca finalizará. Fuga de gorutinas evidente. Con el tiempo, se acumularán decenas de miles de estas gorutinas y el servidor fallará por error de memoria.
En este caso, cerrar el canal es obligatorio. Es precisamente close(ch) lo que da la señal a la go