Глубокое погружение в ключевое слово defer в golang

Go
Глубокое погружение в ключевое слово defer в golang

Ключевое слово 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.

Уведомление:

  1. Оператор defer должен быть перед оператором возврата функции, чтобы он вступил в силу. Следующая программа будет выводить только A
func main() {
    fmt.Println("A")
    return 
    fmt.Println("B")
}
  1. При вызове 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.

Входящие параметры функции отсрочки уже ясны, когда они определены

  1. Входящие параметры функции отсрочки уже ясны, когда они определены.Независимо от того, являются ли входящие параметры переменными, выражениями или операторами функций, фактические результаты параметров будут сначала вычислены, а затем помещены в стек с помощью оператора отсрочки.
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")
}

использованная литература