defer 是 Golang 中一个非常重要的关键字,其在函数返回之前进行调用,进行资源回收、错误处理等工作。
触发时机
A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
阅读官方文档可知,defer 的触发时机主要有三个:
- 函数执行到函数体末端
- 函数执行到 return 语句
- 当前协程 panic
// func
// defer
func test1() {
defer fmt.Println("defer")
fmt.Println("func")
}
// defer
func test2() {
defer fmt.Println("defer")
return
}
// defer
// panic
func test3() {
defer fmt.Println("defer")
panic("panic")
fmt.Println("func")
defer fmt.Println("defer")
}
留意 test3
函数的输出可知:当任意 goroutine 发生 panic 时,会执行当前协程中 panic 之前已声明的 defer 。
执行顺序
若函数中有多个 defer ,执行顺序为 先进后出 ,可理解为栈。
// 3
// 2
// 1
func test() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}
参数确定
defer 在声明时会先计算确定其参数的值,推迟执行的仅是其函数体。同时,defer 满足闭包和匿名函数的特性。
// 5
func test1() (res int) {
defer func(res int) {
res++
}(res)
return 5
}
// 1
func test2() {
i := 1
defer fmt.Println(i)
i = 3
}
// 3
func test3() {
i := 1
defer func() {
fmt.Println(i)
}()
i = 3
}
返回值处理
在调用 return 时,会完成以下的事情:
- 对返回值赋值
- 调用 defer 表达式
- 退出函数,并将返回值给调用函数
匿名返回值
对于匿名返回值函数,其返回值在代码中通常是无法获取的。
// 1
func test() int {
a := 1
defer func() {
a = 2
}()
return a
}
当返回值是指针类型时,我们还是可以修改返回值的。
// 2
func test() *int {
a := 1
p := &a
defer func() {
*p = 2
}()
return &a
}
此外,defer 函数的返回值是会直接被抛弃的。
// 1
func test() int {
res := 1
defer func() int {
res++
return res
}()
return 1
}
命名返回值
对于命名返回值函数,可以在 defer 中修改返回值变量,进而修改函数的返回值。
// 2
func test1() (res int) {
res = 1
defer func() {
res = 2
}()
return
}
// 2
func test2() (res int) {
i := 1
defer func() {
res++
}()
return i
}
注意点
- 主动调用
os.Exit(int)
时,defer 将不执行。 - 在发生 panic 的 goroutine 中,若没有一个 defer 调用
recover()
进行修复,则在执行完之前已声明的 defer 后,进程崩溃。 - defer 只对当前 goroutine 有效,因此当一个没有 defer 调用
recover()
的 goroutine 发生了 panic ,即使其他 goroutine 有 defer 调用recover()
,进程依旧会崩溃。