Перейти к отложенной функции defer в деталях

задняя часть Go

Функция отсрочки defer в языке Go действует как тяжелая задача try...catch, и ее очень легко использовать, однако в практических приложениях многие гоферы не совсем понимают порядок выполнения между отсрочкой, возвратом, возвращаемым значением. , и паникуй, так Падай в яму, сегодня мы раскроем ее тайну!

Сначала запустите следующие два фрагмента кода:

А. Случай анонимных возвращаемых значений

package mainimport (
    "fmt")func main() {
    fmt.Println("a return:", a()) // 打印结果为 a return: 0}func a() int {
    var i int
    defer func() {
        i++
        fmt.Println("a defer2:", i) // 打印结果为 a defer2: 2
    }()
    defer func() {
        i++
        fmt.Println("a defer1:", i) // 打印结果为 a defer1: 1
    }()
    return i}

B. Случай именованного возвращаемого значения

package mainimport (
    "fmt")func main() {
    fmt.Println("b return:", b()) // 打印结果为 b return: 2}func b() (i int) {
    defer func() {
        i++
        fmt.Println("b defer2:", i) // 打印结果为 b defer2: 2
    }()
    defer func() {
        i++
        fmt.Println("b defer1:", i) // 打印结果为 b defer1: 1
    }()
    return i // 或者直接 return 效果相同}

Сперва сделаем вывод(Этоправильный вывод), чтобы помочь вам понять, почему:

  1. Порядок выполнения множественных отсрочек: «последний вошел, первый ушел/первый пришел последним»;

  2. Все функции проверят наличие оператора отсрочки перед выполнением инструкции возврата RET.Если есть, вызовите оператор отсрочки в обратном порядке, чтобы завершить работу, а затем выйти и вернуться;

  3. Анонимное возвращаемое значение объявляется при выполнении return, а именованное возвращаемое значение объявляется одновременно с объявлением функции.Поэтому в операторе отсрочки можно получить доступ только к именованному возвращаемому значению, но анонимное возвращаемое значение нельзя получить напрямую. доступ;

  4. На самом деле, return должен включать два шага до и после: первый шаг — присвоить значение возвращаемому значению (если это именованное возвращаемое значение, присвойте его напрямую, если это анонимное возвращаемое значение, сначала объявите его, а затем назначьте его); второй шаг - вызвать инструкцию возврата RET и передать возвращаемое значение, и RET проверит, существует ли отсрочка. Если она существует, вставьте операторы отсрочки в обратном порядке, и, наконец, RET выйдет из функции с возвращаемым значением;

следовательноПорядок выполнения defer, return и return value должен быть следующим:return сначала присваивает возвращаемое значение, затем defer начинает завершающую работу, наконец, инструкция RET выходит из функции с возвращаемым значением.

Как объяснить разницу между двумя результатами:

Причина, по которой возвращаемые результаты двух приведенных выше фрагментов кода различаются, на самом деле хорошо понятна из приведенных выше выводов.

  • Возвращаемое значение функции a()int не объявляется заранее, его значение получается из присваивания другим переменным, а другие переменные модифицируются в отложенном состоянии (фактически отложенный не может получить прямой доступ к возвращаемому значению), поэтому возвращаемое значение значение при выходе из функции не было изменено.

  • Возвращаемое значение функции b()(i int) объявлено заранее, что позволяет defer получить доступ к возвращаемому значению, поэтому после return присваивает возвращаемое значение i, defer вызывает возвращаемое значение i и модифицирует его, и, наконец, вызывает return вызвать RET для выхода из функции. Возвращаемое значение после этого будет значением, измененным defer.

C. Давайте посмотрим на третий пример, чтобы проверить сделанный выше вывод:

package mainimport (
    "fmt")func main() {
    c:=c()
    fmt.Println("c return:", *c, c) // 打印结果为 c return: 2 0xc082008340}func c() *int {
    var i int
    defer func() {
        i++
        fmt.Println("c defer2:", i, &i) // 打印结果为 c defer2: 2 0xc082008340
    }()
    defer func() {
        i++
        fmt.Println("c defer1:", i, &i) // 打印结果为 c defer1: 1 0xc082008340
    }()
    return &i}

Хотя с()Возвращаемое значение int не объявляется заранее, но благодаря c()Возвращаемое значение int является переменной-указателем, затем после return присваивает адрес переменной i возвращаемому значению, defer снова изменяет фактическое значение i в памяти, поэтому возвращаемое значение, когда return вызывает RET для выхода из функции, по-прежнему остается исходный адрес указателя, но фактическое значение памяти, на которое он указывает, было успешно изменено.

То есть вывод считаем правильным!

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

package mainimport (
    "fmt"
    "time")func main() {
    defer P(time.Now())
    time.Sleep(5e9)
    fmt.Println("main ", time.Now())}func P(t time.Time) {
    fmt.Println("defer", t)
    fmt.Println("P    ", time.Now())}// 输出结果:// main  2017-08-01 14:59:47.547597041 +0800 CST// defer 2017-08-01 14:59:42.545136374 +0800 CST// P     2017-08-01 14:59:47.548833586 +0800 CST

E. Объем отсрочки

  1. defer действителен только для текущей сопрограммы (main можно рассматривать как основную сопрограмму);

  2. Когда какая-либо (основная) сопрограмма вызывает панику, будет выполняться отсрочка, объявленная перед паникой в ​​текущей сопрограмме;

  3. В панике (основной) сопрограмме, если ни один из методов defer не вызывает метод recovery() для восстановления, это приведет к сбою всего процесса после выполнения последнего объявленного метода defer;

  4. При активном вызове os.Exit(int) для выхода из процесса defer больше не будет выполняться.

package mainimport (
    "errors"
    "fmt"
    "time"
    // "os")func main() {
    e := errors.New("error")
    fmt.Println(e)
    // (3)panic(e) // defer 不会执行
    // (4)os.Exit(1) // defer 不会执行
    defer fmt.Println("defer")
    // (1)go func() { panic(e) }() // 会导致 defer 不会执行
    // (2)panic(e) // defer 会执行
    time.Sleep(1e9)
    fmt.Println("over.")
    // (5)os.Exit(1) // defer 不会执行}

F. Вызывающая последовательность выражений defer выполняется в порядке поступления.

Выражения defer помещаются в стековую структуру, поэтому порядок вызововФИФО/Последний пришел, первый вышел. 

Вывод кода ниже — 4321 вместо 1234.

package mainimport (
    "fmt")func main() {
    defer fmt.Print(1)
    defer fmt.Print(2)
    defer fmt.Print(3)
    defer fmt.Print(4)}

Станция перерыва на чай

Небольшая остановка, где вы можете остановиться, посмотреть и помочь вам во время перерыва на кофе.

Контент здесь в основном представляет собой серверные технологии, личное управление, управление командой и другие личные мысли.