Go defer
- defer 的执行顺序
- defer 与 return
- defer 与 函数返回值
- defer 与 panic
- defer 中包含 panic
- defer 下的函数参数包含子函数
defer 的执行顺序
栈,先进后出
func func1() {
fmt.Println("A")
}
func func2() {
fmt.Println("B")
}
func func3() {
fmt.Println("C")
}
func main() {
defer func1()
defer func2()
defer func3()
}
// result:
// C
// B
// A
defer 与 return
在retrun之后,函数结束之前执行; 因为 return 可以包含一些操作,而不是单纯地返回某个值,所以 defer需要在return之后执行
func deferFunc() int {
fmt.Println("defer func called")
return 0
}
func returnFunc() int {
fmt.Println("return func called")
return 0
}
func returnAndDefer() int {
defer deferFunc()
return returnFunc()
}
func main() {
returnAndDefer()
}
// result:
// return func called
// defer func called
defer 与 函数返回值
- 函数的返回值会被初始化为对应类型的零值;
- return 是将"2"这个值赋值给t,函数最终返回的是t的值, 不是"2"。(虽然最终t=2)
- 当defer执行的函数带有形参时,取当前环境的变量
func withDeferRes(i int) (res int) {
// !!!注意!!! defer压栈时,如果有形参则需要在压栈时得到确定的值后传入(当前环境的变量)
defer func(a int) {
res = a
}(i)
// 此时修改i并不会影响之后执行defer
i += 100
fmt.Println("i = ", i)
// res 为int型的零值:0
fmt.Println("res = ", res)
// return将 “2” 赋值给 res, 最终函数返回的是res的值(res会被defer影响)
return 2
}
func withDeferRes2(i int) (res int) {
// 没带形参i
defer func() {
res = i
}()
// !!!注意!!! 此时修改i会影响之后执行defer
i += 100
fmt.Println("i = ", i)
// res 为int型的零值:0
fmt.Println("res = ", res)
// return将 “2” 赋值给 res, 最终函数返回的是res的值(res会被defer影响)
return 2
}
func withDefer3(n1, n2 int) int {
// Println函数存在参数,取当前环境变量n1,不会被后面的 n1++ 影响
defer fmt.Println("n1's value: ", n1)
defer fmt.Println("n2's value:", n2)
n1++
n2++
return n1 + n2
}
func returnButDefer() (t int) {
// 没有形参,defer中的t会被影响
defer func() {
t = t * 10
}()
// 将 “1” 这个值传给t,影响defer
return 1
}
func main() {
withDeferRes(10)
// result:
// i = 110
// res = 0
fmt.Println(withDeferRes(12))
// i = 112
// res = 0
// 12
withDeferRes2(20)
// result:
// i = 120
// res = 0
fmt.Println(withDeferRes2(22))
// i = 122
// res = 0
// 122
withDefer3(99, 999)
// n2's value: 999
// n1's value: 99
fmt.Println(returnButDefer())
// 10
}
defer 与 panic
遇到 panic 时,遍历本协程的 defer 链表,并执行 defer 。保证你的一些资源一定会被关闭。
- 没有遇到 recover,遍历完本协程的 defer 链表后,向 stderr 抛出 panic 信息。
func defer_call() {
defer func() {
fmt.Println("defer: panic 之前 1")
}()
defer func() {
fmt.Println("defer: panic 之前 2")
}()
panic("异常内容") //触发 defer 出栈
defer func() {
fmt.Println("defer: panic 之后,永远执行不到")
}()
}
func main() {
defer_call()
fmt.Println("main 正常结束")
}
// result:
// defer: panic 之前 2
// defer: panic 之前 1
// panic: 异常内容
// ... 异常堆栈信息
- 如果在执行 defer 过程中,遇到 recover 则停止 panic,返回 recover 处继续往下执行。
func defer_call() {
defer func() {
fmt.Println("defer: panic 之前 1, 捕获异常")
if err := recover(); err != nil {
fmt.Println(err)
}
}()
defer func() {
fmt.Println("defer: panic 之前 2, 不捕获")
}()
panic("异常内容") //触发 defer 出栈
defer func() {
fmt.Println("defer: panic 之后, 永远执行不到")
}()
}
func main() {
defer_call()
fmt.Println("main 正常结束")
}
// result:
// defer: panic 之前 2, 不捕获
// defer: panic 之前 1, 捕获异常
// 异常内容
// main 正常结束
- 多个panic,仅有最后一个会被recover()捕获
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
} else {
fmt.Println("fatal")
}
}()
defer func() {
panic("defer panic")
}()
panic("panic")
}
// result:
// defer panic
defer 下的函数参数包含子函数
压栈时需要连同函数地址、函数形参一同进栈。形参如果是个函数则被调用。
func function(index int, value int) int {
fmt.Println(index)
return index
}
//result: 3 1 4 2
func noDefer() {
function(1, function(3, 0))
function(2, function(4, 0))
}
// result: 3 4 2 1
func withDefer() {
// 压栈时有形参,需要先执行function(3, 0), 得到确定的值后再压栈 function(2,3)
defer function(1, function(3, 0))
// 压栈时有形参,需要先执行function(4, 0), 得到确定的值后再压栈 function(2,4)
defer function(2, function(4, 0))
}
//result: 3 1 4 2
func withDefer2() {
// 压栈时没参数
defer func() {
function(1, function(3, 0))
function(2, function(4, 0))
}()
}
func main() {
noDefer()
// result: 3 1 4 2
withDefer()
// result: 3 4 2 1
withDefer2()
// result: 3 1 4 2
}
- 原文作者:zhutou
- 原文链接:https://flygar.org/post/Golang/20200402_defer/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。