ZetCode

Go defer

最后修改于 2025 年 5 月 3 日

在本文中,我们将展示如何在 Golang 中使用 defer 语句延迟执行。

Go 中的 defer 语句允许将函数的执行推迟到其包含的函数完成为止。尽管被延迟,但函数调用的参数在 defer 语句出现的位置立即进行评估。

延迟的函数调用使用堆栈结构进行管理,这意味着它们遵循后进先出 (LIFO) 的执行顺序。因此,当包含的函数开始返回过程时,最近延迟的函数将是第一个执行的。这种行为对于确保正确的资源管理和清理特别有用。

defer 语句的一个常见应用是资源管理任务,例如关闭打开的文件、释放网络连接或释放数据库句柄。通过使用 defer,开发人员可以确保清理操作始终如一地执行,即使函数由于错误或提前返回语句而过早返回。此外,延迟的调用可以与多个 defer 语句结合使用,从而允许按照声明的相反顺序执行多个清理操作。

除了资源清理之外,defer 还可以用于记录执行事件、测量执行时间或确保特定函数的最终化任务可靠运行。

Go defer 语句

以下是 Go defer 语句的一个简单演示。

simple.go
package main

import "fmt"

func main() {

    defer fmt.Println("sky")
    fmt.Println("falcon")
}

我们有两个 Println 函数调用。第一个是用 defer 关键字延迟的。

$ go run simple.go 
falcon
sky

Go defer 参数评估

延迟函数的参数会立即进行评估。

arg_eval.go
package main

import "fmt"

func main() {

    fmt.Println("start")
    for i := 1; i <= 5; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("end")
}

defer 语句放在 for 循环内部。i 变量在循环执行期间进行评估。

$ go run arg_eval.go 
start
end
5
4
3
2
1

Go defer 函数调用顺序

延迟的函数调用被放入一个堆栈中,并按照后进先出 (LIFO) 的顺序调用。

defer_order.go
package main

import "fmt"

func main() {

    defer fmt.Println("one")
    defer fmt.Println("two")
    defer fmt.Println("three")
    defer fmt.Println("four")

    defer fmt.Println("4")
    defer fmt.Println("3")
    defer fmt.Println("2")
    defer fmt.Println("1")
}

defer fmt.Println("one") 最先被延迟,最后执行。defer fmt.Println("1") 最后被延迟,最先执行。

$ go run defer_order.go 
1
2
3
4
four
three
two
one

Go defer 资源释放

defer 语句通常在释放必要资源(如打开的文件)时使用。

words.txt
sky
cloud
cup
wood
rock
sea
tree
oil
book
falcon

这是 words.txt 文件。

read_line_by_line.go
package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {

    f, err := os.Open("words.txt")

    if err != nil {
        log.Fatal(err)
    }

    defer f.Close()

    scanner := bufio.NewScanner(f)

    for scanner.Scan() {

        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

在代码示例中,我们逐行读取打开的文件。

f, err := os.Open("words.txt")

if err != nil {
    log.Fatal(err)
}

defer f.Close()

打开 words.txt 文件并检查错误后,我们延迟调用 Close 方法。它在 main 函数结束时释放打开的文件。

Go defer 与 panic

即使函数发生 panic,延迟的函数调用也会被执行。

defpanic.go
package main

import "fmt"

func main() {
    defer fmt.Println("sky")
    panic("terminating")
    fmt.Println("falcon")
}

该示例在延迟 Println 函数后调用 panic;它仍然会执行。

$ go run defpanic.go 
sky
panic: terminating

goroutine 1 [running]:
main.main()
	/home/janbodnar/Documents/prog/golang/defer/defpanic/defpanic.go:7 +0xb9
exit status 2

来源

The Go Programming Language Specification

在本文中,我们介绍了 Golang 中的 defer 语句。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有超过十年的经验。

列出所有 Go 教程