• Go

Go Defer

Go中的defer关键字表示了要在当前函数结束时执行的函数。常用于释放资源(例如关闭文件句柄或解锁互斥锁)

Defer语句是以关键字defer为前缀的普通函数调用,即defer someFunction()

如:

func foo() {
  f, err := os.Open("myfile.txt")
  if err != nil {
    return
  }
  defer f.Close()

  // 其他代码
  // 其他代码
}

在上面的示例中,f.Close()将在foo函数退出前执行。

defer f.Close()紧随os.Open()之后,可以轻松确保始终调用Close.

也使用匿名函数延迟执行多条语句:

func foo() {
  mutex1.Lock()
  mutex2.Lock()

  defer func() {
    mutex2.Unlock()
    mutex1.Unlock()
  }()

  // ... more code
}

多个defer

如果一个函数中有多个defer语句,它们将形成一个栈。 最后一个defer是在函数返回之后第一个执行的,然后依次调用先前的defer(以下示例通过触发panic而返回):

func logNum(i int) {
    fmt.Printf("Num %d\n", i)
}

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

    // not deferred since execution flow never reaches this line
    defer logNum(4)
}

First main statement
Num 3
Num 2
Num 1
panic: panic occurred
goroutine 1 [running]:
main.main()
/tmp/src963563549/main.go:17 +0x152
exit status 2

defer函数在执行时会计算其参数:

func logNum(i int) {
    fmt.Printf("Num %d\n", i)
}

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

First main statement
Num 4
Num 2
Num 1

如果一个函数具有命名的返回值,则即使该函数返回,该函数中的defer匿名函数也可以访问和更新返回的值:

func plusOne(i int) (result int) {
    // anonymous function must be called by adding ()
    defer func() { result++ }()

    // i is returned as result, which is updated by deferred function above
    // after execution of below return
    return i
}

func main() {
    fmt.Println(plusOne(1)) // 2
}

2

Defer pitfalls

使用defer时,请牢记以下几点:defer函数在函数结束时调用。

defer语句是函数范围,而不是代码块范围, 换句话说:defer调用在退出函数时执行,而不是在执行使用if或for语句创建的块时执行。

func main() {
    fmt.Print("Before if\n")
    if true {
        defer fmt.Print("inside if\n")
    }

    fmt.Print("Ater if\n")
}

Before if
Ater if
inside if

你可能希望该defer语句在我们退出if分支时执行,实际上是作为函数调用的最后一件事执行。

在defer函数中使用外部变量:

func main() {
    for i := 0; i < 2; i++ {
        defer func() {
            fmt.Printf("%d\n", i)
        }()
    }
}

2
2

一个常见的错误是认为该代码将输出0和1。当我们看到defer时,这些就是i的值。

但是,defer创建一个仅通过引用捕获变量i的闭包。 它不捕获变量的值。

当我们看什么代码执行时,应该变得很清楚:

var i int
for i = 0; i < 2; i++ {
    // create closure that references i by reference
}

fmt.Printf("%d\n", i) // from created by defer in second loop iteration (remember: reverse order)
fmt.Printf("%d\n", i) // from closure created by defer in first loop iteration

现在很明显,当我们调用deferred fmt.Printf时,我是2。

我们可以通过强制捕获变量来解决此问题:

func main() {
    for i := 0; i < 2; i++ {
        defer func(i2 int) {
            fmt.Printf("%d\n", i2)
        }(i)
    }
}

1
0

闭包可能会稍微昂贵一些,因为它需要分配一个对象来收集闭包捕获的所有变量。


相关

最新