defer откладывает выполнение функции до выхода из окружающей функции — даже если выход происходит из-за паники или return. Когда defer используется внутри цикла, все отложенные вызовы накапливаются в стеке вызовов defer и выполняются в обратном порядке при завершении окружающей функции.
Это может привести к неожиданному расходу ресурсов и задержкам, ведь все отложенные функции вызовутся одновременно только после выхода из тела функции, а не после каждой итерации цикла.
Пример кода:
func readFiles(files []string) { for _, name := range files { f, _ := os.Open(name) defer f.Close() // ресурсы освобождаются только после завершения всей функции // обработка файла ... } }
В данном примере файлы будут оставаться открытыми до конца выполнения функции, что может привести к утечке дескрипторов при большом количестве файлов.
Что произойдет, если использовать defer для закрытия ресурсов внутри цикла? Почему это не всегда оптимально?
Ответ: Все defer-вызовы накапливаются и сработают только после завершения функции, а не после каждой итерации. Это приведет к тому, что ресурсы (например, открытые файлы) будут освобождены слишком поздно.
Правильно:
for _, name := range files { f, _ := os.Open(name) // defer f.Close() // нельзя! // Правильно: process(f) f.Close() }
История
На проекте загрузки логов возникла проблема: служба внезапно перестала открывать новые файлы, хотя файлов было мало. Причина — defer в цикле. Все файлы открывались, а их закрытие откладывалось до конца функции. После переписывания на явный Close() после обработки проблема исчезла.
История
В сервисе, собирающем метрики для больших списков, использовался defer для сброса соединения к базе внутри цикла обхода данных. С ростом числа итераций возникли задержки и превышение порога открытых соединений, сервис начал падать по ошибкам "too many open connections".
История
Инженер, надеясь на "элегантную" уборку, применил defer в цикле чтения большого числа файлов, что привело к переполнению лимита открытых дескрипторов на сервере в продакшне и остановке сервиса.