本文整理的 defer
的全场景使用情况。
defer 的执行顺序
Go语言中的defer
语句会将其后面跟随的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说“栈的关系”,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行。
defer 与 return 谁先谁后
在Go语言的函数中return
语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer
语句执行的时机就在返回值赋值操作后,RET指令执行前。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| func f1() int { x := 5 defer func() { x++ }() return x }
func f2() (x int) { defer func() { x++ }() return 5 }
func f3() (y int) { x := 5 defer func() { x++ }() return x } func f4() (x int) { defer func(x int) { x++ }(x) return 5 } func main() { fmt.Println(f1()) fmt.Println(f2()) fmt.Println(f3()) fmt.Println(f4()) }
|
defer 遇见 panic
遇到 panic 时,会触发defer出栈执行 defer。在执行 defer 过程中:遇到 recover 则停止 panic,返回 recover 处继续往下执行。如果没有遇到 recover,执行完本协程的所有 defer 后,向 stderr 抛出 panic 信息。
defer 遇见 panic,但是并不捕获异常的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func main() { defer_func()
fmt.Println("main 正常结束") }
func defer_func() { defer func() { fmt.Println("defer1") }() defer func() { fmt.Println("defer2") }()
panic("异常信息")
defer func() { fmt.Println("defer3: 在panic之后,永远执行不到") }() }
|
结果
1 2 3 4
| defer2 defer1 panic: 异常信息 //... 异常堆栈信息
|
defer 遇见 panic,并捕获异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func main() { defer_func()
fmt.Println("main 正常结束") }
func defer_func() {
defer func() { fmt.Println("defer1:捕获异常") if err := recover(); err != nil { fmt.Println(err) } }()
defer func() { fmt.Println("defer2") }()
panic("异常信息")
defer func() { fmt.Println("defer3: panic 之后, 永远执行不到") }() }
|
结果
1 2 3 4
| defer2 defer1:捕获异常 异常信息 main 正常结束
|
defer 中包含 panic
panic 仅有最后一个可以被 revover 捕获。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func main() {
defer func() { if err := recover(); err != nil{ fmt.Println(err) }else { fmt.Println("fatal") } }()
defer func() { panic("defer panic2") }()
panic("panic1") }
|
结果
当执行到panic("panic1")
语句后,触发第一个panic,然后defer 顺序出栈执行,先执行panic("defer panic2")
,这panic会覆盖的第一个panic,最后执行第二个defer,所以捕获了第二个panic。
defer 下的函数参数包含子函数
defer入栈时,需要把函数地址、函数形参一同压进栈
- 例子1:
1 2 3 4 5 6 7 8 9 10 11
| 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 一共会压栈两次,先进栈 1,后进栈 2。 那么在压栈function(1, function(3, 0))
的时候,需要把函数地址、函数形参一同压进栈,那么为了得到第二个参数的结果,所以就需要先执行 function(3, 0)
,将第二个参数算出,所以第一个结果是3;同理,function(2, function(4, 0))
也一样。
- 闭包引用外部环境变量(创建新的副本)
1 2 3 4 5 6 7 8 9 10 11 12
| func DeferFunc() (t int) { defer func(i int) { fmt.Println(i) fmt.Println(t) }(t) t = 1 return 2 }
func main() { DeferFunc() }
|
结果
(1)初始化返回值 t 为零值 0
(2)defer入栈,同时需要把它对应的函数参数也入栈,因为t=0
,所以i=0
,所以第一个println会打印0
(3)t被赋值为1,然后return,t被赋值为2,最后第二个println打印2
- 闭包引用外部环境变量
1 2 3 4 5 6 7 8
| func main() { var whatever [6]struct{} for i := range whatever { defer func() { fmt.Println(i) }() } }
|
结果:
闭包=函数+引用环境,在 for 循环结束后,局部变量 i 的值已经是 5 了,并且defer的闭包是直接引用变量的 i,由于是引用,所以i跟着变化。
- defer链式调用,优先从左到右计算,只对最后的函数入栈
1 2 3 4 5 6 7 8 9 10 11 12
| type temp struct{}
func (t *temp) Add(elem int) *temp { fmt.Println(elem) return &temp{} }
func main() { tt := &temp{} defer tt.Add(1).Add(5).Add(2) tt.Add(3) }
|
结果
defer压栈只会压最后一个Add,前两个Add会在压栈前优先从左往右计算。
defer遇见exit
1 2 3 4 5 6 7 8 9 10
| func test1() { fmt.Println("test") }
func main() { fmt.Println("main start") defer test1() fmt.Println("main end") os.Exit(0) }
|
结果
如果在函数里是因为执行了os.Exit而退出,而不是正常return退出或者panic退出,那程序会立即停止,被defer的函数调用不会执行。
被defer的函数或方法的参数的值在执行到defer语句的时候就被确定下来了
1 2 3 4 5 6
| func a() { i := 0 defer fmt.Println(i) i++ return }
|
因为执行defer时候,对i的值进行入栈操作,此时i是0,所以是0入栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func bar() (r int) { defer func() { r += 4 if recover() != nil { r += 8 } }() var f func() defer f() f = func() { r += 2 }
return 1 }
func main() { println(bar()) }
|
最终打印13