Подтвердить что ты не робот

Использование отсрочки в голанге

Каково использование defer в golang? Языковая документация говорит, что она выполняется, когда возвращается окружающая функция. Почему бы просто не поместить код в конце данной функции?

4b9b3361

Ответ 1

Обычно мы используем defer для закрытия или освобождения ресурсов.

Окружающая функция выполняет все отложенные вызовы функций перед возвратом, даже если она паникует. Если вы просто помещаете вызов функции в конец окружающей функции, он пропускается, когда происходит паника.

Более того, отложенный вызов функции может справиться с паникой, вызвав встроенную функцию recover. Это не может быть сделано обычным вызовом функции в конце функции.

Каждый отложенный вызов помещается в стек и выполняется в обратном порядке, когда завершается окружающая функция. Обратный порядок помогает правильно освободить ресурсы.

Оператор defer должен быть достигнут для вызова функции.

Вы можете думать об этом как о другом способе реализации блоков try-catch-finally.

Закрытие как try-finally:

func main() {
    f, err := os.Create("file")
    if err != nil {
        panic("cannot create file")
    }
    defer f.Close()
    // no matter what happens here file will be closed
    // for sake of simplicity I skip checking close result
    fmt.Fprintf(f,"hello")
}

Закрытие и обработка паники как try-catch-finally

func main() {
    defer func() {
        msg := recover()
        fmt.Println(msg)
    }()
    f, err := os.Create(".") // . is a current directory
    if err != nil {
        panic("cannot create file")
    }
    defer f.Close()
    // no matter what happens here file will be closed
    // for sake of simplicity I skip checking close result
    fmt.Fprintf(f,"hello")
}

Преимущество по сравнению с try-catch-finally заключается в отсутствии вложенности блоков и переменных областей. Это упрощает структуру окружающей функции.

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

func yes() (text string) {
    defer func() {
       text = "no"
    }()
    return "yes"
}

func main() {
    fmt.Println(yes())
}

Ответ 2

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

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

Также можно отложить несколько раз в одной и той же функции. Например: если у вас есть разные ресурсы, выделяемые через вашу функцию, которые должны быть в конечном итоге освобождены до их возвращения, вы можете вызывать отсрочку для каждого из них после выделения, и эти функции выполняются в обратном порядке последовательности, в которой они были вызваны когда функция выходит.

Ответ 4

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

Так что это дает хорошие вещи:

  1. Восстановление после panic. Это позволяет да понять, try... catch поведение.

  2. Не забудьте очистить (закрыть файлы, освободить память и т.д.) Перед обычным выходом. Вы можете открыть какой-либо ресурс, и вы должны закрыть его перед выходом. Но функция может иметь несколько точек выхода, поэтому вы должны добавить освобождение в каждую точку возврата. Это очень утомительно в обслуживании. Или вы можете поставить только один отложенный оператор - и ресурсы будут освобождены автоматически.

Ответ 5

Здесь уже есть хорошие ответы. Я хотел бы упомянуть еще один вариант использования.

func BillCustomer(c *Customer) error {
    c.mutex.Lock()
    defer c.mutex.Unlock()

    if err := c.Bill(); err != nil {
        return err
    }

    if err := c.Notify(); err != nil {
        return err
    }

    // ... do more stuff ...

    return nil
}

defer в этом примере гарантирует, что независимо от того, как возвращается BillCustomer, mutex будет unlocked непосредственно перед BillCustomer. Это чрезвычайно полезно, потому что без defer вам придется помнить, чтобы unlock mutex в каждом месте, которое функция могла бы return.

ссылка

Ответ 6

defer выражение отсрочивает выполнение функции до окружающей функции возвращает.

Этот пример демонстрирует defer функциональность:

func elapsed(what string) func() {
    start := time.Now()
    fmt.Println("start")
    return func() {
        fmt.Printf("%s took %v\n", what, time.Since(start))
    }
}

func main() {
    defer elapsed("page")()
    time.Sleep(time.Second * 3)
}

Из:

start
page took 3s

Ответ 7

  1. Языки программирования стремятся предоставлять конструкции, которые облегчают более простую и менее подверженную ошибкам разработку. (Например, почему Golang должен поддерживать сборку мусора, когда мы можем освободить память самостоятельно)
  2. Функция может возвращаться в нескольких точках. Пользователь может пропустить выполнение некоторых операций очистки в некоторых путях
  3. Некоторые операции очистки не относятся ко всем путям возврата
  4. Кроме того, лучше держать код очистки ближе к исходной операции, которая требовала очистки
  5. Когда мы выполняем определенные операции, требующие очистки, мы можем "планировать" операции очистки, которые будут выполняться, когда функция вернется независимо от того, какой путь произойдет.