
defer в Go — это мощный механизм для очистки ресурсов, закрытия файлов и разблокировки мьютексов. Вы наверняка слышали, что defer делает код чище и безопаснее.
Когда вы открываете файл через os.Open() или os.Create(), Go выделяет ресурс операционной системы — дескриптор файла.
И вот в чём важный момент:
-
Этот дескриптор нужно обязательно закрыть через
file.Close(). -
Иначе файл останется «висеть» открытым — ресурсы будут утекать, программа начнёт захлёбываться или упадёт.
Мьютекс (mutex = MUTual EXclusion) — это замок, который нужен, чтобы упорядочить доступ к общим данным из разных потоков (goroutines).
-
Только одна горутина может «захватить» мьютекс в один момент времени.
-
Остальные горутины будут ждать, пока замок не освободится.
Мьютекс — это способ сказать: «Сейчас только я работаю с этим куском данных, остальные — подождите!»
Но не всегда очевеидно, что если использовать defer бездумно, это может привести к серьёзным проблемам: снижению производительности, излишним аллокациям и неявным перегрузкам в runtime.
defer и инлайн: почему важно?
Go компилятор умеет делать инлайн функций — это значит, что вместо реального вызова код функции просто вставляется прямо в месте вызова. Это очень сильно ускоряет программу.
Но если в функции есть defer, инлайн невозможен. Почему? Потому что defer меняет логику выхода из функции, требуя дополнительную обработку.
Сколько стоит defer на практике?
Простой пример:
package main import ( "fmt" "time" ) var x int func withDefer() { defer func() {}() } func withoutDefer() { x++ } func main() { const iterations = 100_000_000 start := time.Now() for i := 0; i < iterations; i++ { withDefer() } fmt.Printf("withDefer: %v\n", time.Since(start)) start = time.Now() for i := 0; i < iterations; i++ { withoutDefer() } fmt.Printf("withoutDefer: %v\n", time.Since(start)) }
Результаты:
go run main.go
withDefer: 89.974ms
withoutDefer: 132.4544ms
defer замедляет работу функции на порядок!
Как узнать, заинлайнил ли Go функцию?
Запустите:
go run -gcflags="-m" main.go
Вы увидите вывод типа:
./main.go:7:6: can inline add ./main.go:11:6: cannot inline withDefer: function contains defer ./main.go:16:10: inlining call to add
Здесь прямо написано: можно инлайнить или нельзя.
defer в runtime
Когда вы пишите defer, что происходит внутри?
-
Выделяется структура для deferred-вызовов.
-
В неё записываются функции и их аргументы.
-
При выходе из функции: выполняются все deferred-функции в обратном порядке.
Оптимизации
Начиная с Go 1.14 введены fast defer оптимизации, которые уменьшают накладные расходы на defer. Но в горячих циклах всё равно лучше избегать defer, если это возможно.
Вывод
-
deferчище код, но дешевым его не делает. -
В горячих участках кода
deferнужно использовать с осторожностью и только там, где это действительно оправдано. -
Смотрите вывод
-gcflags="-m", чтобы понять, заинлайнилась ли ваша функция
Горячий участок кода — это часть программы, которая выполняется очень часто или тратит много времени процессора.
Код внутри циклов, особенно больших (
for,while),Код, который вызывается миллионы раз (например, маленькие функции в обработке сетевых запросов),
Код, который напрямую влияет на быстродействие всей программы.
ссылка на оригинал статьи https://habr.com/ru/articles/904718/
Добавить комментарий