一、defer
1、 defer延迟调用,完成一些收尾工作。无论函数或方法是否出错,一定会在退出当前函数或者方法之前调用传入的函数(只对函数或方法生效,代码块不生效),常被用于关闭文件描述符、关闭数据库连接以及解锁资源。
2、defer的实现原理
编译器会将 defer
关键字都转换成 runtime.deferproc
函数,负责创建新的延迟调用;runtime.deferreturn
函数负责在函数调用结束时执行所有的延迟调用,runtime.deferreturn
函数会多次判断当前 Goroutine 的 _defer
链表中是否有未执行的剩余结构,在所有的延迟函数调用都执行完成之后,该函数才会返回。
3、多次调用 defer 时的执行顺序问题:
后调用的defer函数会先执行,先调用的defer函数会后执行。内部其实是一个defer链表,会绑定到当前的goroutine。
defer
关键字插入时是从后向前的,而 defer
关键字执行是从前向后的,而这就是后调用的 defer
会优先执行的原因。
4、 defer的传值方式及预计算问题:
defer的传值采用值传递,调用defer时会立刻对函数中引用的外部参数进行拷贝进行值传递,导致实际执行时。
func main() {
startedAt := time.Now()
defer fmt.Println(time.Since(startedAt))
time.Sleep(time.Second)
}
解决此问题可以向 defer 关键字传入匿名函数,虽然调用defer时也使用值传递,但是因为拷贝的是函数指针,所以 time.Since(startedAt) 会在 main 函数返回前被调用并打印出符合预期的结果。
func main() {
startedAt := time.Now()
defer func() { fmt.Println(time.Since(startedAt)) }()
time.Sleep(time.Second)
}
二、panic和recover
panic,调用panic时会立刻停止执行函数的其他代码,并触发当前Goroutine的defer链表(defer要定义在panic之前,否则defer不生效);
recover,可以中止panic造成的程序崩溃,且只能定义在defer中。
触发panic时,会先打印defer的函数再打印异常堆栈信息
package main import "fmt" func main() { defer func() { fmt.Println("a")
}() defer func() { fmt.Println("b")
}() defer func() { fmt.Println("c")
}() panic("error")
}
打印:
GOROOT=D:\Go #gosetup
GOPATH=D:\goland\workspace #gosetup
D:\Go\bin\go.exe build -o C:\Users\80310624\AppData\Local\Temp\___1go_build_main_go.exe D:\goland\workspace\src\proj_test01\main.go #gosetup
C:\Users\80310624\AppData\Local\Temp\___1go_build_main_go.exe #gosetup
c
b
a
panic: error
goroutine 1 [running]:
main.main()
D:/goland/workspace/src/proj_test01/main.go:15 +0x85
Process finished with exit code 2