推遲基礎知識

Go 中的延遲語句只是一個標記為稍後執行的函式呼叫。Defer 語句是一個以關鍵字 defer 為字首的普通函式呼叫。

defer someFunction()

一旦包含 defer 語句的函式返回,就會執行延遲函式。封閉函式發生對延遲函式的實際呼叫:

  • 執行 return 語句
  • 落下了
  • 恐慌

例:

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)
}

輸出:

First main statement
Last main statement
Function main returned

如果函式具有多個延遲語句,則它們形成堆疊。最後一個 defer 是在封閉函式返回後執行的第一個,然後按順序呼叫前面的 defers(下面的例子通過引起恐慌返回):

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)
}

輸出:

First main statement
Num 3
Num 2
Num 1
panic: panic occurred

goroutine 1 [running]:
....

請注意,延遲函式在 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)
}

輸出:

First main statement
Num 4
Num 2
Num 1

如果函式已命名返回值,則該函式中的延遲匿名函式即使在函式返回後也可以訪問和更新返回的值:

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
}

最後,defer 語句通常用於經常一起出現的操作。例如:

  • 開啟和關閉檔案
  • 連線和斷開連線
  • 鎖定和解鎖互斥鎖
  • 將一個等待組標記為已完成(defer wg.Done()

無論執行流程如何,此用途都可確保正確釋放系統資源。

resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close() // Body will always get closed