Найти в Дзене
Roman Pankov Tech

Про defer в Golang

В Go ключевое слово defer используется для откладывания выполнения функции до момента выхода из текущей функции. Очень полезный функционал, когда нужно освободить какие-то ресурсы (закрыть файл, канал, соединение с базой и т.д.) Пример Если в функции объявлено несколько defer, то они выполняются по принципу LIFO (last in, first out), последний пришёл, первый ушёл Так же нужно понимать, что аргументы для функции объявленой в defer будут вычисляться в момент определения. В общем, вот реальный пример использовать defer Так же defer может использоваться для обработки паники Когда компилятор встречает ключевое слово defer, он создаёт специальную структуру _defer расположенную в runtime/runtime2.go в структуре g. По-простому говоря, в горутине есть стек отложенных вызовов. g - это по факту внутреннее представление горутины в рантайме. Про структуру g подробней разберём в следующих статьях. Давайте разберём каждое поле. heap bool - флаг, указывающий, где хранится defer, в куче или на ст
Оглавление

Немного теория

В Go ключевое слово defer используется для откладывания выполнения функции до момента выхода из текущей функции. Очень полезный функционал, когда нужно освободить какие-то ресурсы (закрыть файл, канал, соединение с базой и т.д.)

Пример

-2

Если в функции объявлено несколько defer, то они выполняются по принципу LIFO (last in, first out), последний пришёл, первый ушёл

-3

Так же нужно понимать, что аргументы для функции объявленой в defer будут вычисляться в момент определения.

-4

В общем, вот реальный пример использовать defer

-5

Так же defer может использоваться для обработки паники

-6

Внутреннее устройство defer

Когда компилятор встречает ключевое слово defer, он создаёт специальную структуру _defer расположенную в runtime/runtime2.go в структуре g. По-простому говоря, в горутине есть стек отложенных вызовов.

g - это по факту внутреннее представление горутины в рантайме. Про структуру g подробней разберём в следующих статьях.

-7

Давайте разберём каждое поле.

heap bool - флаг, указывающий, где хранится defer, в куче или на стеке. Данный флаг необходим, если стек горутины будет переносится(да так иногда бывает), тогда Go нужно перенести defer которые хранятся в стеке. При этом, если defer хранится в куче, с ним по факту делать ничего не нужно.

До версии 1.14 все defer создавались в куче. Это просто и понятно. Начиная с 1.4 добавили оптимизацию Open-coded defer и теперь defer может быть так же на стеке.

sp uintptr - это значение(адрес в памяти) регистра Stack Pointer (указателя стека) в конкретный момент времени. Это значение необходимо, если стек "переезжает"(стек вырос и нужно скопировать данные в новый участок памяти ) или стек "схлопнулся" (при выходе из функции)

pc uintptr - это значение(адрес в памяти) регистра Program Counter, которое необходимо для возврата, после выполнения отложенной функции.

fn func() - это сам defer-функция, которую необходимо вызвать.

link *_defer - в функции может быть объявлено много defer'ов. Они хранятся в виде связанного списка. Значение данного поле хранит ссылку на следующий defer.

rangefunc bool — флаг, который указывает на оптимизацию выполнения defer внутри цикла for range по функции-генератору (например, по каналу или функции, возвращающей итератор). Начиная с Go 1.20, компилятор группирует все отложенные вызовы defer, возникшие в ходе одной итерации, и помещает их в отдельный атомарный список head *atomic.Pointer[_defer]. Это позволяет значительно снизить накладные расходы на выделение памяти и повысить производительность.

head *atomic.Pointer[_defer] - если флаг rangefunc=true, в этом поле хранится ссылка начало связанного списка defer'ов.

rangefunc bool - флаг для оптимизации defer внутри циклов. Начиная с Go 1.20, компилятор может по-особому обрабатывать defer внутри цикла, если цикл использует for range по функции-генератору (например, по каналу или функции, возвращающей итератор).

Что-то вроде резюме

  • defer хорошо использовать для отложенных функций который должны освободить ресурсы(закрыть канал/файл и т.д.), что-то залогировать и т.д.
  • Не стоит использовать defer для громоздких задач (вроде запрос к внешнему API)
  • defer'ы вызываются по принципу LIFO - последний пришёл первый ушёл
  • defer'ы вызываются даже если произошла паника
  • Вычисление параметров для defer'а происходят в момент объявления
  • В каждой горутине свой стек defer'ов

🔥 Заходи в Телегу, там есть что почитать!