Szukaj…
Wprowadzenie
Instrukcja defer
wypycha wywołanie funkcji na listę. Lista zapisanych wywołań jest wykonywana po powrocie otaczającej funkcji. Odroczenie jest powszechnie stosowane w celu uproszczenia funkcji wykonujących różne czynności czyszczenia.
Składnia
- odłóż someFunc (args)
- defer func () {// kod idzie tutaj} ()
Uwagi
Odroczenie działa poprzez wstrzyknięcie nowej ramki stosu (funkcja wywoływana po słowie kluczowym defer
) do stosu wywołań poniżej aktualnie wykonywanej funkcji. Oznacza to, że odroczenie jest gwarantowane tak długo, jak stos będzie rozwijany (jeśli twój program ulegnie awarii lub otrzyma SIGKILL
, odroczenie nie zostanie wykonane).
Odłóż podstawy
Instrukcja odroczenia w Go jest po prostu wywołaniem funkcji oznaczonym do wykonania w późniejszym czasie. Instrukcja defer
jest zwykłym wywołaniem funkcji poprzedzonym słowem kluczowym defer
.
defer someFunction()
Funkcja odroczona jest wykonywana po zwróceniu funkcji zawierającej instrukcję defer
. Rzeczywiste wywołanie funkcji odroczonej występuje, gdy funkcja zamykająca:
- wykonuje instrukcję return
- spada z końca
- panika
Przykład:
func main() {
fmt.Println("First main statement")
defer logExit("main") // position of defer statement here does not matter
fmt.Println("Last main statement")
}
func logExit(name string) {
fmt.Printf("Function %s returned\n", name)
}
Wynik:
First main statement
Last main statement
Function main returned
Jeśli funkcja ma wiele odroczonych instrukcji, tworzą one stos. Ostatnie defer
jest pierwszym, które zostanie wykonane po zwróceniu funkcji zamykającej, a następnie kolejne wywołania w kolejności defer
w kolejności (poniżej przykład zwraca, powodując panikę):
func main() {
defer logNum(1)
fmt.Println("First main statement")
defer logNum(2)
defer logNum(3)
panic("panic occurred")
fmt.Println("Last main statement") // not printed
defer logNum(3) // not deferred since execution flow never reaches this line
}
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
Wynik:
First main statement
Num 3
Num 2
Num 1
panic: panic occurred
goroutine 1 [running]:
....
Zauważ, że funkcje odroczone są poddawane ocenie ich argumentów w czasie wykonywania defer
:
func main() {
i := 1
defer logNum(i) // deferred function call: logNum(1)
fmt.Println("First main statement")
i++
defer logNum(i) // deferred function call: logNum(2)
defer logNum(i*i) // deferred function call: logNum(4)
return // explicit return
}
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
Wynik:
First main statement
Num 4
Num 2
Num 1
Jeśli funkcja nazywa wartości zwrotne, odroczona funkcja anonimowa w ramach tej funkcji może uzyskać dostęp do zaktualizowanej wartości i zaktualizować ją nawet po jej zwróceniu:
func main() {
fmt.Println(plusOne(1)) // 2
return
}
func plusOne(i int) (result int) { // overkill! only for demonstration
defer func() {result += 1}() // anonymous function must be called by adding ()
// i is returned as result, which is updated by deferred function above
// after execution of below return
return i
}
Wreszcie, instrukcja defer
to zwykle używane operacje, które często występują razem. Na przykład:
- otwórz i zamknij plik
- połączyć i rozłączyć
- zablokuj i odblokuj muteks
- oznacz grupę oczekującą jako wykonaną (
defer wg.Done()
)
Takie zastosowanie zapewnia prawidłowe zwolnienie zasobów systemowych niezależnie od przepływu wykonania.
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close() // Body will always get closed
Odroczone wywołania funkcji
Odroczone wywołania funkcji służą do podobnego celu, jak na przykład finally
bloki w językach takich jak Java: zapewniają, że niektóre funkcje zostaną wykonane, gdy funkcja zewnętrzna powróci, niezależnie od tego, czy wystąpił błąd lub która instrukcja return została trafiona w przypadkach z wieloma zwrotami. Jest to przydatne do czyszczenia zasobów, które należy zamknąć, takich jak połączenia sieciowe lub wskaźniki plików. defer
kluczowe defer
wskazuje odroczenie wywołania funkcji, podobnie jak słowo kluczowe go
inicjujące nowy goroutine. Podobnie jak wywołanie go
, argumenty funkcji są analizowane natychmiast, ale w przeciwieństwie do wywołania go
, odroczone funkcje nie są wykonywane jednocześnie.
func MyFunc() {
conn := GetConnection() // Some kind of connection that must be closed.
defer conn.Close() // Will be executed when MyFunc returns, regardless of how.
// Do some things...
if someCondition {
return // conn.Close() will be called
}
// Do more things
}// Implicit return - conn.Close() will still be called
Zwróć uwagę na użycie conn.Close()
zamiast conn.Close
- nie tylko przekazujesz funkcję, ale odraczasz pełne wywołanie funkcji, w tym jej argumenty. Wiele wywołań funkcji można odroczyć w tej samej funkcji zewnętrznej, a każde z nich zostanie wykonane raz w odwrotnej kolejności. Możesz także odkładać zamknięcia - po prostu nie zapomnij o parens!
defer func(){
// Do some cleanup
}()