Ключевое слово defer в golang используется для объявления функции отсрочки, которая будет помещена в список, и система выполнит функцию отсрочки до возврата внешней функции оператора отсрочки. Особенности отсрочки:
- Выполнить до возврата функции
- Может быть размещен в любом месте функции
- Несколько функций отсрочки могут быть установлены одновременно, и выполнение нескольких функций отсрочки следует порядку FILO.
- Входящие параметры функции отсрочки уже ясны, когда они определены
- Именованные возвращаемые значения в функциях можно модифицировать
- Используется для освобождения и закрытия файловых ресурсов, блокировки ресурсов, подключений к базе данных и т. д.
- Обработка паники с помощью восстановления
defer будет выполнен до возврата функции
Когда программа выполняет функцию, контекст функции (входные параметры, возвращаемые значения, выходные параметры и другая информация) помещается в стек памяти программы в виде кадра стека.После выполнения функции устанавливается возвращаемое значение В это время стек Кадр извлекается из стека, и функция фактически завершает выполнение.
Функция оператора defer будет выполнена до возврата из функции, и следующая программа последовательно выведет B и A:
func main() {
defer fmt.Println("A")
fmt.Println("B")
}
defer можно разместить в любом месте функции
func main() {
fmt.Println("A")
defer fmt.Println("B")
fmt.Println("C")
}
Вышеприведенная программа будет последовательно выводить A C B.
Уведомление:
- Оператор defer должен быть перед оператором возврата функции, чтобы он вступил в силу. Следующая программа будет выводить только A
func main() {
fmt.Println("A")
return
fmt.Println("B")
}
- При вызове os.Exit defer не будет выполняться. Следующая программа будет выводить только B
func main() {
defer fmt.Println("A")
fmt.Println("B")
os.Exit(0)
}
Несколько функций отсрочки могут быть установлены одновременно
Можно установить несколько функций отсрочки, и выполнение нескольких функций отсрочки следует порядку FILO.Следующая программа будет выводить B D C A по очереди.
func main() {
defer fmt.Println("A")
fmt.Println("B")
defer fmt.Println("C")
fmt.Println("D")
}
Давайте рассмотрим несколько случаев вложенности отложенных действий:
func main() {
fmt.Println("A")
defer func() {
fmt.Println("B")
defer fmt.Println("C")
fmt.Println("D")
}()
defer fmt.Println("E")
fmt.Println("F")
}
Приведенная выше программа будет последовательно выводить: A F E B D C
Внутренняя форма реализации оператора defer представляет собой структуру:
# 位于/usr/lib/go/src/runtime/runtime2.go#784
type _defer struct {
...
sp uintptr // 函数栈指针,sp是stack pointor单词首字母缩写
pc uintptr //程序计数器, pc是program counter单词首字母缩写
fn *funcval // 函数地址,执行defer函数
_panic *_panic // 指向最近一次panic
link *_defer // 指向下一个_defer结构
...
}
Внутренняя реализация defer — это связанный список, а тип элемента связанного списка —_deferструктура, из которыхlinkполе указывает на следующее_deferАдрес, когда оператор отсрочки определен, система преобразует функцию отсрочки в структуру _defer и помещает ее в начало связанного списка.Когда он наконец будет выполнен, система будет выполняться последовательно, начиная с заголовка связанного списка, который является результатом нескольких отсрочек.Порядок выполнения является причиной для First In Last Out.
Входящие параметры функции отсрочки уже ясны, когда они определены
- Входящие параметры функции отсрочки уже ясны, когда они определены.Независимо от того, являются ли входящие параметры переменными, выражениями или операторами функций, фактические результаты параметров будут сначала вычислены, а затем помещены в стек с помощью оператора отсрочки.
func main() {
i := 1
defer fmt.Println(i)
i++
return
}
Вышеприведенная программа выводит 1, а не 2
Уведомление:
Когда defer используется как замыкание, доступ всегда является последним значением в цикле.
func main() {
for i:=0; i<5; i++ {
defer func() {
fmt.Println(i)
}()
}
}
Вышеупомянутая программа выводит 5 последовательных 5s
Решением может быть передача значения в замыкающую функцию, в это время при проталкивании функции отложенного в стек будет записываться не только адрес стека, но и входящие параметры, и значение стека будет распечатан при выполнении.
func main() {
for i:=0; i<5; i++ {
defer func(i int) {
fmt.Println(i)
}(i)
}
}
В это время выведите 4 3 2 10 последовательно
Именованные возвращаемые значения в функциях можно модифицировать
Следующая программа выводит 101 и выполняетreturn 100В это время 100 будет скопировано и возвращено в переменную i, а затем будет выполнена функция отсрочки, и значение i будет увеличено на 1. В это время значение i станет 101, и, наконец, функциональный тест будет фактически выполнен, поэтому вывод на печать будет 101
func main() {
fmt.Println(test())
}
func test() (i int) {
defer func() {
i++
}()
return 100
}
УведомлениеВ случае анонимного возвращаемого значения следующая программа выводит 1 вместо 101.
При выполнении тестовой функции система сгенерирует временную переменную в качестве переменной возвращаемого значения.return retВ это время в эту временную переменную будет скопировано значение ret, после чего функция defer не имеет ничего общего с переменной ret, поэтому окончательное возвращаемое значение тестовой функции равно 1.
func main() {
fmt.Println(test())
}
func test() int {
ret := 1
defer func() {
ret += 100
}()
return ret
}
Давайте рассмотрим случай, когда возвращаемое значение является анонимным указателем:
func main() {
fmt.Println(*(test()))
}
func test() *int {
ret := 1
defer func() {
ret += 100
}()
return &ret
}
Приведенная выше программа выведет 101 по той же причине, что и выше.
Он в основном используется для закрытия файловых ресурсов, закрытия соединения с базой данных и т. д.
Высвобождение и переработка ресурсов
С помощью defer мы можем решать проблемы повторного использования ресурсов лаконично и элегантно, избегая игнорирования связанных проблем повторного использования ресурсов в случае сложной логики кода.
Давайте посмотрим на следующий код, цель которого — скопировать содержимое файла в новый файл.
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
dst, err := os.Create(dstName)
if err != nil {
return
}
written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
Приведенный выше код содержит ошибки. При сбое создания файла он возвращается напрямую, но не закрывает повторное использование открытых файловых ресурсов.
С помощью отсрочки мы можем гарантировать, что ресурсы всегда могут быть правильно закрыты, а логика обработки проста и элегантна.
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
Обработка паники с помощью восстановления
Пользователь восстановления перехватывает исключения паники, а паника используется для создания исключений. recovery необходимо поместить в оператор defer, иначе его нельзя будет захватить один раз.
В следующем примере фиксируется паника и выводится информация о панике.
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
panic("it is panic")
}
При одновременном возникновении нескольких аварийных ситуаций перехватывается только первая паника.
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
panic("it is panic")
panic("it is another panic")
}