Почему defer f.Close() внутри цикла — это ошибка и как правильно ограничить очистку?
Этот processFiles открывает и читает каждый файл из среза путей. На длинном списке он падает с too many open files.
func processFiles(paths []string) error {
for _, p := range paths {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
// ... читаем f ...
}
return nil
}
Найдите и исправьте ошибку.
defer выполняется при возврате функции, а не каждую итерацию, поэтому каждый f.Close() не закрывается до выхода из функции — на многих файлах исчерпываются дескрипторы. Та же ловушка с defer mu.Unlock() даёт самоблокировку на следующей итерации. Исправление — вынести тело цикла в функцию, возвращающуюся каждый раз.
- ✗Думать, что
deferвыполняется в конце каждой итерации, а не при возврате функции - ✗Считать, что накопленные
defer f.Close()безвредны, ведь ОС вернёт дескрипторы при выходе - ✗Упускать, что
defer mu.Unlock()в цикле даёт самоблокировку на второй итерации
- →Как оборачивание тела цикла в замыкание или функцию ограничивает
deferодной итерацией? - →Почему
defer mu.Unlock()в цикле приводит к deadlock, аdefer f.Close()лишь течёт?
Найдите ошибку
func processFiles(paths []string) error {
for _, p := range paths {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close() // ОШИБКА: defer-ы копятся до возврата функции
// ... читаем f ...
}
return nil
}
Почему это ошибка
defer привязан к возврату функции, а не к итерации цикла. Каждый виток добавляет ещё один отложенный f.Close(), но ни один из них не выполняется, пока processFiles не вернётся. На длинном списке открытые файлы накапливаются, и процесс исчерпывает файловые дескрипторы (too many open files).
⚠️ Та же ловушка с блокировкой опаснее: defer mu.Unlock() в цикле не отпускает mutex между итерациями, поэтому вторая итерация снова берёт mu.Lock() на уже захваченном мьютексе → самоблокировка.
✅ Исправление — ограничить область defer одной итерацией, вынеся тело в функцию:
func processFiles(paths []string) error {
for _, p := range paths {
if err := processOne(p); err != nil {
return err
}
}
return nil
}
func processOne(p string) error {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close() // закрывается при выходе из processOne — каждую итерацию
// ... читаем f ...
return nil
}