defer pospone la ejecución de una función hasta que se sale de la función circundante, incluso si la salida es debido a un pánico o un return. Cuando defer se usa dentro de un ciclo, todas las llamadas diferidas se acumulan en la pila de llamadas defer y se ejecutan en orden inverso al finalizar la función circundante.
Esto puede llevar a un consumo inesperado de recursos y retrasos, ya que todas las funciones diferidas se invocarán simultáneamente solo después de salir del cuerpo de la función, y no después de cada iteración del ciclo.
Ejemplo de código:
func readFiles(files []string) { for _, name := range files { f, _ := os.Open(name) defer f.Close() // los recursos se liberan solo después de que se complete toda la función // procesamiento del archivo ... } }
En este ejemplo, los archivos permanecerán abiertos hasta el final de la ejecución de la función, lo que puede llevar a una fuga de descriptores con una gran cantidad de archivos.
¿Qué pasará si usas defer para cerrar recursos dentro de un ciclo? ¿Por qué no siempre es óptimo?
Respuesta: Todas las llamadas defer se acumulan y solo se activarán después de que se complete la función, y no después de cada iteración. Esto hará que los recursos (por ejemplo, archivos abiertos) se liberen demasiado tarde.
Correcto:
for _, name := range files { f, _ := os.Open(name) // defer f.Close() // ¡no se puede! // Correcto: process(f) f.Close() }
Historia
En un proyecto de carga de logs surgió un problema: el servicio dejó de abrir nuevos archivos de repente, aunque había pocos archivos. La razón — defer en el ciclo. Todos los archivos se abrían, y su cierre se pospuso hasta el final de la función. Después de reescribir a un Close() explícito después del procesamiento, el problema desapareció.
Historia
En un servicio que reúne métricas para listas grandes, se usó defer para cerrar la conexión a la base de datos dentro del ciclo de iteración de datos. Con el aumento en el número de iteraciones, surgieron retrasos y superación del umbral de conexiones abiertas, el servicio comenzó a fallar por errores de "demasiadas conexiones abiertas".
Historia
Un ingeniero, confiando en una limpieza "elegante", aplicó defer en un ciclo de lectura de un gran número de archivos, lo que llevó al desbordamiento del límite de descriptores abiertos en el servidor de producción y la detención del servicio.