Go语言defer详解
1.defer概述:
defer是用来声明一个延迟函数,并且将这个函数放到一个栈中,它的调用时间在return执行之前,详细来讲,它的执行时间在return的值赋值之后,在返回之前。通常可以用来做一些资源的释放等。
具体规则
1)defer的内容虽然会在函数的最后执行,但是defer内的参数会在defer语句声明时就确定。
2)defer之后内容会在defer栈中,先压入栈的最后执行,也就是最先声明的defer函数会最后才执行。
这么设置的原因是defer’被设计用来释放资源,因为资源一般都有依赖性,所以需要先释放最后调用的资源,因此设计为FILO
3)defer可以操作主函数的具名返回值
defer的执行时间在函数对return的值赋值之后,
defer的实现底层结构
//defer 的结构和一般的函数类似,包含栈指针,程序计数器,函数地址等
type _defer struct {
sp uintptr //函数栈指针
pc uintptr //程序计数器
fn *funcval //函数地址
link *_defer //指向自身结构的指针,用于链接多个defer
}
2.defer的常见用法
1)拦截panic
defer的运行机制决定了无论函数是执行到函数末尾返回还是中途返回又或者是中途遇到了panic,此时已经声明的defer依旧会被调度执行,因此可以拦截painc根据需要对panic进行处理。并且可以尝试从panic中恢复。
遇到panic时,遍历本协程的defer链表,并执行defer。在执行defer过程中:遇到recover则停止panic,返回recover处继续往下执行。如果没有遇到recover,遍历完本协程的defer链表后,向stderr抛出panic信息。
2)修改函数的具名返回值
3)输出调试信息
可以用来输出日志等信息。
4)还原变量旧值
在Go标准库中,syscall包下有使用过,将变量旧值赋给零一变量,使用defer函数还原
3.关于defer的几个关键问题
1.明确哪些函数可以做为defered函数
对于有返回值的自定义函数或者方法,返回值会在defered函数调用时被自动丢弃,对于有返回值的内建函数,直接defer会panic,因此可以使用一个包裹它的匿名函数来满足要求。
defer func () { //使用 _ 来接收append返回的结果,这时就不会panic
_ = append(s1,11)
}()
2.把握defer后表达式的求值时机
defer关键字后的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。即在函数中声明时就已经对表达式求值。
3.知晓defer带来的性能损耗
在1.14版本之后,defer的性能损耗变得很低
4.defer三个重要的函数运行结果
defer函数定义时,对外部变量有两种引用方式,分别是作为函数参数和作为闭包引用。作为函数参数,在defer时就会将值传递给defer然后压入defered栈作为闭包引用的话,则会在defer函数真正调用时根据整个上下文来确定当前的值。
defer后面的语句在执行的时候,函数调用的参数会被保存起来,也就是复制了一份。真正执行的时候,实际上用到的是这个复制的变量,因此如果此变量是一个“值”,那么就和定义的时候是一致的。如果此变量是一个“引用”,那么就可能和定义的时候不一致。
func d1 (){
for i := 3; i > 0 ; i -- {
j := i
defer func () {
fmt.Println(j)
}()
}
}
//此函数的输出值为1 2 3
func d2 (){
for i := 3; i > 0 ; i -- {
defer func () {
fmt.Println(i)
}()
}
}
//此函数的输出值为 0 0 0
//因为匿名函数不包含参数,所以这一延迟函数在for循环之后被评估
func d3 (){
for i := 3; i > 0 ; i -- {
defer func (n int) {
fmt.Println(n)
}(i)
}
}
//此函数的输出值为1 2 3
第三个代码块为最佳解决方案。将变量的值传入匿名函数中
defer包含panic
defer func() {
if err := recover(); err != nil{
fmt.Println(err)
}else {
fmt.Println("fatal")
}
}()
defer func() {
panic("defer panic")
}()
panic("panic") //代码执行结果为defer panic
panic只有最后一个可以被recover函数捕获,当上述代码触发painc时,会触发defer执行,defer中的panic将会覆盖掉之前的panic被捕获