defer是什么
defer是Go语言提供的一种用于注册延迟调用的机制,让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。
defer与panic和recover结合,形成了go语言风格的异常与捕获机制
常用于一些成对操作的场景:
打开连接/关闭连接;加锁/释放锁;打开文件/关闭文件等。
package main import "os" func main() { f, err := os.Open(filename) if err != nil { panic(err) } if f != nil { defer f.Close() } }
优点:方便使用
缺点:有性能损耗
每次defer语句执行时,会把函数“压栈”,函数参数会被拷贝下来
当外层函数(非代码块,eg一个for循环)退出时,defer函数按照定义的逆序执行
如果defer执行的函数为nil,那么会在最终调用函数产生panic
defer参数
在defer函数定义时,对外部变量的引用是有两种方式的
1.作为函数参数
在defer定义时就把值传递给defer,并被cache起来
2. 作为闭包引用
在defer函数真正调用时根据整个上下文确定当前的值
闭包
闭包=函数+引用环境
go的所有匿名函数都是闭包
package main import "fmt" func main() { var a = Accumulator() fmt.Printf("%d\n", a(1)) fmt.Printf("%d\n", a(10)) fmt.Printf("%d\n", a(100)) fmt.Println("------------------------") var b = Accumulator() fmt.Printf("%d\n", b(1)) fmt.Printf("%d\n", b(10)) fmt.Printf("%d\n", b(100)) } func Accumulator() func(int) int { var x int return func(delta int) int { fmt.Printf("(%+v,%+v) - ", &x, x) x += delta return x } }
运行结果
(0xc000016098,0) - 1 (0xc000016098,1) - 11 (0xc000016098,11) - 111 ------------------------ (0xc0000160f0,0) - 1 (0xc0000160f0,1) - 11 (0xc0000160f0,11) - 111
闭包引用了x变量,a,b可看作2个不同的实例,实例之间互不影响。实例内部,x变量是同一个地址,因此具有“累加效应”。
defer易错场景
type number
int func (n number)print(){
fmt.Println(n)
}
func (n*number)pprint(){
fmt.Println(*n)
}
func main(){
var n number
defer n.print()
defer n.pprint()
defer func(){
n.print()
}()
defer func (){
n.pprint()
}()
n = 3
}
第四个defer语句是闭包,引用外部函数的n, 最终结果是3;
第三个defer语句同第四个;
第二个defer语句,n是引用,最终求值是3.
第一个defer语句,对n直接求值,开始的时候n=0, 所以最后是0;
defer拆解
return xxx
经过编译后变成三条指令
返回值 = xxx
调用defer函数
空的return
例一
package main import "fmt" func f() (r int) { t := 5 defer func() { t = t + 5 }() return t } func main() { fmt.Println(f())//5 }
拆解后
package main import "fmt" func f() (r int) { t := 5 //1.赋值指令 r = t //2.defer被插入到赋值与返回之间执行,返回值r未被修改过 func() { t = t + 5 }() //3.空的return指令 return } func main() { fmt.Println(f()) }
例二
package main import "fmt" func f() (r int) { t := 5 //1.赋值指令 //2.这里改的t是之前传值传进去的t,不会改变要返回的t defer func(t int) { t = t + 5 }(t) //3.空的return指令 return t } func main() { fmt.Println(f())//5 }
package main import ( "errors" "fmt" ) func f1() { var err error defer fmt.Println(err) err = errors.New("defer error") return } func f2() { var err error defer func() { fmt.Println(err) }() err = errors.New("defer error") return } func f3() { var err error defer func(err error) { fmt.Println(err) }(err) err = errors.New("defer error") return } func main() { f1() f2() f3() }
运行结果:
<nil> defer error <nil>
执行顺序 1->2->3
第1,3个函数是因为作为函数参数,定义的时候就会求值,定义的时候err变量的值都是nil, 所以最后打印的时候都是nil.
第2个函数的参数其实也是会在定义的时候求值,只不过,第2个例子中是一个闭包,它引用的变量err在执行的时候最终变成
defer error
了
defer配合recover
一次偶然的请求可能会触发某个bug, 这时用recover捕获panic, 稳住主流程,不影响其他请求。
recover()函数只在defer的上下文中才有效(且只有通过在defer中用匿名函数调用才有效),直接调用的话,只会返回
nil
.package main import ( "fmt" "os" "time" ) func main() { defer fmt.Println("defer main") var user = os.Getenv("USER_") go func() { defer func() { fmt.Println("defer caller") if err := recover(); err != nil { fmt.Println("recover success. err", err) } }() func() { defer func() { fmt.Println("defer here") }() if user == "" { panic("should set user env.") } //此处不会执行 fmt.Println("after panic") }() }() time.Sleep(100) fmt.Println("end of main function") }
输出
defer here defer caller recover success. err should set user env. end of main function defer main
注意:
panic后的defer不会被执行(遇到panic,如果没有捕获错误,函数会立即终止)
panic没有recover时,抛出的panic到当前goroutine最上层函数时,最上层程序直接异常终止
package main import "fmt" func F() { defer func() { fmt.Println("b") }() panic("a") } func main() { defer func() { fmt.Println("c") }() F() fmt.Println("继续执行") } /* b c panic: a */
panic有被recover时,当前goroutine最上层函数正常执行
package main import "fmt" func F() { defer func() { if err := recover(); err != nil { fmt.Println("捕获异常", err) } fmt.Println("b") }() panic("a") } func main() { defer func() { fmt.Println("c") }() F() fmt.Println("继续执行") } /* 捕获异常 a b 继续执行 c */