Defer
延时操作,在函数return
时执行的操作
一般用来释放锁资源,关闭文件等清理操作。
在多处返回时,可以简化清理流程。
捕获异常
使用场景
mu.Lock()
defer mu.Unlock()
f,err := os.Open(filename)
if err != nil {
panic(err)
}
if f != nil {
defer f.Close()
}
defer触发时机
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
Go
官方文档中对defer的执行时机做了阐述,分别是。
- 包裹
defer
的函数返回时 - 包裹
defer
的函数执行到末尾时 - 所在的
goroutine
发生panic
时
注意事项
后进先出
defer 的执行顺序是后进先出
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}
// Output
// 3
// 2
// 1
出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执 行,最后才会执行panic
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
panic("my panic")
}
// Output
// 3
// 2
// 1
// panic: my panic
func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()
panic("触发异常")
}
func main() {
fmt.Println("Start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
defer_call()
panic("my panic")
fmt.Println("Done")
}
// Output
// Start
// 打印后
// 打印中
// 打印前
// 3
// 2
// 1
// panic: 触发异常
defer的嵌套
defer在当前函数return时执行
踩坑点
使用 defer 最容易采坑的地方是和带命名返回参数的函数一起使用时。
defer 语句定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。作为函数参数,则在 defer 定义时就把值传递给 defer,并被缓存起来;作为闭包引用的话,则会在 defer 函数真正调用时根据整个上下文确定当前的值。
return xxx
等价于
返回值 = xxx
调用 defer 函数
空的 return
func increaseA() int {
var i int
defer func() {
i++
}()
return i
}
// 0
// annoy = i
// i++
// return
func increaseB() (r int) {
defer func() {
r++
}()
return r
}
// 1
func f1() (r int) {
defer func() {
r++
}()
return 0
}
// 1
func f2() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
// 5
func f3() (r int) {
defer func() {
r = r + 5
}()
return 1
}
// 6
func f4() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
// 1
func main() {
var whatever [3]struct{}
for i := range whatever {
defer func() {
fmt.Println(i)
}()
}
}
// 2
// 2
// 2
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
}
// 3 闭包,引用外部函数的n, 最终结果是3
// 3 同第四个
// 3 n是引用,最终求值是3
// 0 对n直接求值,开始的时候n=0, 所以最后是0
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")
}()
fmt.Println("Goroutine Done")
}()
time.Sleep(time.Second)
fmt.Println("end of main function")
}
// Output
// defer here
// defer caller
// recover success. err: should set user env.
// -------
// end of main function
// defer main
Panic
嵌套panic
func main() {
defer fmt.Println("in main")
defer func() {
defer func() {
panic("panic again and again")
}()
panic("panic again")
}()
panic("panic once")
}
$ go run main.go
in main
panic: panic once
panic: panic again
panic: panic again and again
goroutine 1 [running]:
...
exit status 2
编译器会将关键字 panic
转换成 runtime.gopanic
编译器会将关键字 recover
转换成 runtime.gorecover
package main
import "fmt"
func test2() {
defer func() {
fmt.Println("defer Test02")
if r := recover(); r != nil {
fmt.Println("recover", r)
panic("test2 defer panic")
}
}()
panic("test2 panic")
fmt.Println("test2 Done")
}
func test1() {
defer func() {
fmt.Println("defer Test01")
if r := recover(); r != nil {
fmt.Println("recover", r)
}
}()
test2()
fmt.Println("test1 Done")
}
func main() {
defer func() { fmt.Println("defer main") }()
test1()
fmt.Println("main Done")
}
// OutPut:
// defer Test02
// recover test2 panic
// defer Test01
// recover test2 defer panic
// main Done
// defer main
实现详解
编译器会将 defer
关键字都转换成 runtime.deferproc
函数,函数负责创建新的延迟调用
它还会为所有调用 defer
的函数末尾插入 runtime.deferreturn
的函数调用,函数负责在函数调用结束时执行所有需要的的延迟调用
runtime的几种 panic
优化点
栈上分配
开放编码
defer
deferreturn
https://studygolang.com/articles/5932
https://juejin.im/post/5b9b4acde51d450e5071d51f
5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里!
https://segmentfault.com/a/1190000018169295#articleHeader4
https://qcrao.com/2020/03/23/how-to-traverse-defer-links/
本文由 LeonardWang 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jul 22,2020