怎么理解golang的defer

115 阅读3分钟

本文为译文,希望在大家学习的过程中能带给大家帮助😘

原文地址 Golang's "defer" Explained (With Examples)

If you just want to see the code, you can find the complete examples on Github


基础用法

defer关键字指示一个函数在其包裹函数执行完成之后再执行。

package main

import "fmt"

func main() {
 // When we add `defer` before a function, that function is executed
 // after the surrounding function completes
 defer fmt.Println("this is printed once the function completes")

 fmt.Println("this is printed first")
 fmt.Println("this is printed second")
}

输出:

// this is printed first
// this is printed second
// this is printed once the function completes

函数的作用域范围

我们需要注意:defer只是作用于被包裹的函数内。我们看如下的例子

package main

import "fmt"

func main() {
 defer fmt.Println("this is printed once main completes")

 greet()

 fmt.Println("this is printed after greet is called")
}

func greet() {

 // 当greet函数执行完成后,这个函数会立刻执行
 defer fmt.Println("printed after the first greeting")

 fmt.Println("first greeting")
}

输出如下:

// first greeting
// printed after the first greeting
// this is printed after greet is called
// this is printed once main completes

执行序列(调用栈)

当我们的函数中存在多个deferred,它们将会被存贮然后以堆栈的顺序执行。即先进后出。

func myFunction() {
 defer fmt.Println("first")
 defer fmt.Println("second")
 defer fmt.Println("third")

 fmt.Println("starting myFunction...")
 // ...
}

输出

starting myFunction...
third
second
first

每次,每个函数被推迟后,他就会被塞入一个栈中。一旦包裹函数执行完成,被推迟的函数就会被弹出。

使用带有panic的defer

在go中如果一个函数发生错误后就会产生panic,这种情况下函数会立刻退出,其后边所有的语句都不会被执行。

假如defer的函数在panic发生之前,那么defer的函数仍会在函数退出之前被调用。

package main

import "fmt"

func main() {
 defer fmt.Println("this is printed once the function panics")

 fmt.Println("this is printed before panic")
 panic("something went wrong")
 fmt.Println("this should not be printed")
} 
// this is printed before panic
// this is printed once the function panics
// panic: something went wrong

// goroutine 1 [running]:
// main.main()
//         /Users/soham/src/github.com/sohamkamani/go-defer-example/defer-with-panic.go:11 +0xe4
// exit status 2

实际上,我们可以使用内置的recover函数来判断是否发生了panic.

这个只能在derfer函数内完成,我们需要明确的说明,当panic发生的时候需要做些什么

package main

import "fmt"

func main() {
 defer func() {
  // we can use the recover function inside the deferred function
  // to tell if it was called after `panic` was called
  if r := recover(); r != nil {
   fmt.Println("recovered from panic: ", r)
  }
 }()

 panic("something went wrong")
 fmt.Println("this should not be printed")
}
// this is printed before panic
// recovered from panic:  something went wrong

argument evaluation

一般包裹函数执行完成后会执行derfer的函数,但是defer函数的参数会立刻执行。 defer函数的传入参数在定义时就已经明确,不论传入的参数是变量、表达式、函数语句,都会先计算出计算出实参结果,再随defer语句入栈

package main

import "fmt"

// i is a package level variable
var i int

// here, we increment the value of i every time this
// function is called
func count() int {
 i++
 return i
}

func main() {

 // `count` is called here first
 defer fmt.Println("deferred count:", count())

 fmt.Println("current count:", count())
}
current count: 2
deferred count: 1
package main

import "fmt"

var i int

func count() int {
 i++
 return i
}

func main() {

 defer printCount()

 fmt.Println("current count:", count())
}

// Now, count is evaluated inside `printCount`, so it will
// only be called when the deferred function executes
func printCount() {
 fmt.Println("count:", count())
}
current count: 1
count: 2

Do a test.what would be the output of this code.

func main() {
 fmt.Println(zeroOrOne())
}

func zeroOrOne() (i int) {
 defer func() {
  i++
 }()

 return i
}
1

在函数返回之前,推迟的函数会被执行。

上面的例子中,包裹函数zeroOrone执行完成后,变量i会被增加。

何时该用defer

defer比较像java python中的finally块。在尝试运行一些无论成功或者失败的代码后,代码块都会被执行。

当你在代码执行完成后需要做一些清洗的工作的时候,这个能力就比较重要了。例如

  • 在一个文件打开后然后关闭它。
  • 在运行一个test后然后reset它。

额外的,你可以处理任何panic在其发生之后。

defer主要我们应该注意的问题在于argument evaluation&order of execution

你之前有使用过defer么,面临了哪些问题,可以在留言区告诉我。我们可以一块探索讨论。