Defer
defer 用于注册延迟调用:函数返回前、或 panic 发生时,被 defer 的语句会按照「后进先出」的顺序执行。它常用于资源释放(关闭文件、解锁 mutex、恢复 panic)。
执行时机的关键规则:return xxx 并不是原子操作,而是分两步——先把返回值赋值,再执行 defer,最后真正返回。因此 defer 中修改命名返回值会影响最终返回结果,而修改普通局部变量则不会。
- stack
包内执行过程中,defer 存储在栈空间
2. return
defer 执行在 return 后,先执行 return 的内容,在按照栈内存储顺序从栈顶向下依次执行 defer 语句
3. panic
遇到 panic 之后,按照出栈的方式执行 defer 函数,直到被 recover() 函数捕获
- recover 函数捕获异常
- 栈内的
defer仍然会执行,直到空栈
Examples-Panic-Without-Recover
package main
import (
"fmt"
)
func main() {
defer_call()
fmt.Println("main 正常结束")
}
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 之后,永远执行不到") }()
}
/**
defer: panic 之前2
defer: panic 之前1
panic: 异常内容
//... 异常堆栈信息**/
Examples-Panic-With-Recover
package main
import "fmt"
func main() {
defer_call()
fmt.Println("main 正常结束")
}
func defer_call() {
defer func() { fmt.Println("defer: before recover()") }()
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println("defer: panic 之前1, 捕获异常")
}()
defer func() { fmt.Println("defer: panic 之前2, 不捕获") }()
panic("异常内容") //触发defer出栈
defer func() { fmt.Println("defer: panic 之后, 永远执行不到") }()
}
/**
defer: panic 之前2, 不捕获
异常内容
defer: panic 之前1, 捕获异常
defer: before recover()
main 正常结束**/ //recover()defer func()之前的defer也会执行,在输出Panic内容后,会执行完所有的defer栈
Examples-Multi-Panic-With-Recover
panic仅有最后一个可以被revover捕获
package main
import (
"fmt"
)
func main() {
defer func() {
if err := recover(); err != nil{
fmt.Println(err)
}else {
fmt.Println("fatal")
}
}()
defer func() {
panic("defer panic")
}()
panic("panic")
}
/**
defer panic**/
Examples-Defer-With-Nested-Func
defer 下的函数包含子函数的话:
- 在入栈函数的时候,需要获取该函数所有的参数
- 因此如果函数中嵌套了子函数的话,在入栈时会先执行这个子函数
package main
import "fmt"
func function(index int, value int) int {
fmt.Println(index)
return index
}
func main() {
defer function(1, function(3, 0))
defer function(2, function(4, 0))
}
/**
defer压栈function1,压栈函数地址、形参1、形参2(调用function3) --> 打印3
defer压栈function2,压栈函数地址、形参1、形参2(调用function4) --> 打印4
defer出栈function2, 调用function2 --> 打印2
defer出栈function1, 调用function1--> 打印1**/
package main
import "fmt"
func DeferFunc1(i int) (t int) {
t = i //赋值t=1
defer func() {
t += 3 //defer 执行在return之后,返回 t = 4
}()
return t //t=1
}
func DeferFunc2(i int) int {
t := i //t =1
defer func() {
t += 3 //局部变量
}()
return t //将t赋值给返回的值 返回t=1
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i // 返回 t + 1 = 3
}()
return 2 //t = 2
}
func DeferFunc4() (t int) {
defer func(i int) {
fmt.Println(i) //i = 0
fmt.Println(t) // t =2
}(t)
t = 1
return 2 //t = 2
}
func main() {
fmt.Println(DeferFunc1(1))
fmt.Println(DeferFunc2(1))
fmt.Println(DeferFunc3(1))
DeferFunc4()
}
输出与解析:
4
1
3
0
2
DeferFunc1返回4:命名返回值t,return t先把t=1赋给返回值,defer中t += 3修改的就是返回值本身,因此最终返回4。DeferFunc2返回1:匿名返回值,return t时把局部变量t的值1拷贝给返回值,之后defer修改的只是局部变量t,不影响已拷贝出去的返回值。DeferFunc3返回3:命名返回值t,return 2把2赋给t,defer中t += i(i=1)作用于返回值,最终3。DeferFunc4打印0和2:defer语句的参数在 defer 注册时求值,此时t还是零值0,所以匿名函数里i=0;而t是闭包引用,defer真正执行时t已被return 2赋为2,故打印2。