简介
今天谈谈go的两个特性,defer和panic, defer在函数return 时,将返回值压入栈,然后执行defer函数,最后返回。panic是手动触发崩溃的一种策略,可以在panic本层的函数实现defer函数,在defer里通过recover捕获该层崩溃,如果本层崩溃未被捕获,则交由上一层捕获。
defer机制和使用
一个函数定义了多个defer函数,defer的调用顺序和栈一样,先进后出,最先调用的是最后写的defer。函数将返回值入栈,然后执行析构,在析构之前要执行defer的操作。defer使用的注意事项
defer常用来释放变量
我们实现一个文件copy函数,将src路径下的文件copy到dst路径下
1 | func CopyFile(dst, src string) (written int64, err error){ |
注意,如果Open失败或者Create失败,千万不要调用src.Close,因为src为nil。
defer被声明时,其参数是实时解析和捕获
1 | func DeferParam() { |
程序输出0,因为defer声明时捕获i的值为0,传入函数后输出也是0,不管以后i变成什么值。
如果defer是无参函数,内部引用了外部变量,就不同了,会记录i的引用
1 |
|
最后i变为什么值,defer就输出什么值。此时输出值为1
defer调用顺序为栈式调用
1 | func DeferOrder() { |
输出结果为4,3,2,1,0
defer可以捕获函数返回值
因为defer可以捕获函数内变量,所以可以捕获函数的返回值
1 | func DeferReturn() (res int) { |
defer输出为1,因为defer捕获到返回值为0,+1就输出1
defer链式调用
defer 只能执行一个函数,defer是栈式调用,后入先出规则。当defer执行链式操作时,前边的表达式都会优先求值,只有最后一个表达式入栈延迟执行。
1 | type Slice []int |
s.AddSlice(1)优先被计算,然后是s.AddSlice(2),最后是AddSlice(3),输出值为123
panic
我们在开发阶段可以在关键错误处使用panic触发程序崩溃,也可以在重要的逻辑不允许有异常是做逻辑判断,不符合逻辑的调用panic异常崩溃。
我们先实现两个函数
1 | func Funlv1() { |
在main函数中调用
1 | Funlv2() |
程序输出
1 | Funlv2 begin |
没有输出Funlv2 end 和 Funlv1 end, 因为Panic之后的逻辑不会执行,Funlv1内部panic后,不会输出Funlv1 end, 这个崩溃抛给上层Funlv2,也没有捕获,导致Funlv2也不会输出Funlv2 end,所以无论是否panic,defer函数都会执行。我们要做的是通过recover捕获panic错误,看下改进的代码
1 | func Funlv1Safe() { |
在main函数中调用
1 | Funlv2Safe() |
输出
1 | Funlv2 begin |
由于Funlv1调用panic所以不会输出Funlv1 end 但是Funlv1的defer函数通过recover捕获了panic,所以Funlv2可以正常执行并结束。如果我们注释掉Funlv1的defer函数中的recover,就会由Funlv2来捕获panic
注意如果是捕获本层panic,一定要将defer写在panic之上,否则无法捕获,recover也要写在defer函数或者上层函数中。
panic要等待defer结束后才会向上传递,如果defer捕获了panic就不传递了
1 | func defer_call() { |
所以上述代码调用输出为打印后,打印中,打印前,最后panic触发异常
总结
1 panic执行后,后续语句不再执行,会执行defer函数,如有多个defer就遵循栈式调用
2 如果某个goroutine没有捕获panic,则整个进程崩溃,不仅仅是该goroutine崩溃
3 panic被本层recover后,会影响到本层函数panic之后的语句执行,不影响当前goroutine的执行。
4 recover必须写在defer语句或者上层函数才可生效
5 recover的作用是保证捕获异常后程序可以继续稳定运行。