推迟基础知识

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