defer的作用
go中的defer是延迟函数,一般是用于释放资源或者收尾工作。
由于defer是具有延迟特性且执行动作是在函数return之后,因此作为资源释放作用再好不过。
- 典型例子:释放锁、关闭文件、关闭链接等
// 释放锁
func getValue() {
s.Lock()
defer s.Unlock()
...
}
// 关闭文件
func read() {
f, err := os.OpenFile("filename", os.O_RDWR, os.ModePerm)
defer f.Close()
...
}
// 关闭链接
func connect() {
resp, err := grequests.Get(s.cfg.GateApiCrossMarginUrl, nil)
defer resp.Close()
...
}
// 收尾工作,defer 同时也是函数,可以做很多收尾相关工作
func closeConnection() {
...
defer func() {
file.close()
close(readChan)
}
...
}
- 还有作用就是捕获 panic,这个功能在defer里也是典型用法
go
代码解读
复制代码
func sendChan() {
// 此处捕获 panic 进行 recover 防止程序崩溃
defer func() {
if ok := recover(); ok != nil {
fmt.Println("recover")
}
}()
// 向已经关闭的chan发送数据,此处会引起 panic
dataChan <- "message"
...
}
资源释放动作一定紧跟资源使用(打开、连接)语句,不然defer可能不会被执行到,导致内存泄露
defer执行细节
执行顺序及对返回值的影响
func Run2() {
fmt.Println("a return:", a()) // 打印结果为 a return: 0
fmt.Println("b return:", b()) // 打印结果为 b return: 2
fmt.Println("c return:", c()) // 打印结果为 c return: 2
}
func a() int {
var i int
defer func() {
i++
fmt.Println("a defer2:", i) // 打印结果为 a defer2: 2
}()
defer func() {
i++
fmt.Println("a defer1:", i) // 打印结果为 a defer1: 1
}()
return i
}
func b() (i int) {
defer func() {
i++
fmt.Println("b defer2:", i) // 打印结果为 b defer2: 2
}()
defer func() {
i++
fmt.Println("b defer1:", i) // 打印结果为 b defer1: 1
}()
return i // 或者直接 return 效果相同
}
func c() (i int) {
defer func() {
i++
fmt.Println("b defer2:", i) // 打印结果为 c defer2: 2
}()
defer func() {
i++
fmt.Println("b defer1:", i) // 打印结果为 c defer1: 1
}()
return 0 // 或者直接 return 效果相同
}
根据上面的例子可以看出:
- 多个 defer 的执行顺序为“后进先出/先进后出”
- defer、return、返回值三者的执行顺序应该是:return最先给返回值赋值;接着 defer 开始执行一些收尾工作;最后 RET 指令携带返回值退出函数
- 在返回变量被提前声明的情况下,return操作是将值赋给返回变量
defer函数传参
还需注意,defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体
func main() {
defer P(time.Now())
time.Sleep(5e9)
fmt.Println("main ", time.Now())
}
func P(t time.Time) {
fmt.Println("defer", t)
fmt.Println("P ", time.Now())
}
// main 2024-08-07 09:55:54.443061 +0800 CST m=+5.001461710
// defer 2024-08-07 09:55:49.441861 +0800 CST m=+0.000252501
// P 2024-08-07 09:55:54.443939 +0800 CST m=+5.002339376
defer panic
一般情况下,在程序里记录错误日志,就可以帮助我们在碰到异常时快速定位问题。
但还有一些错误比较严重的,比如数组越界访问,程序会主动调用 panic 来抛出异常,然后程序退出。
如果不想程序退出的话,可以使用 recover 函数来捕获并恢复。
-
defer 只对当前协程有效(main 可以看作是主协程);
-
当任意一条(主)协程发生 panic 时,会执行当前协程中 panic 之前已声明的 defer;
panic会在调用栈上逐级传播,执行每一层的defer语句 -
在发生 panic 的(主)协程中,如果
defer语句中调用recover并成功恢复panic,panic的传播就会停止;如果没有一个 defer 调用 recover()进行恢复,则会在执行完最后一个已声明的 defer 后,引发整个进程崩溃; -
主动调用 os.Exit(int) 退出进程时,defer 将不再被执行。
func main() {
fmt.Println("Start of main")
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in main:", r)
}
}()
f1()
fmt.Println("End of main")
}
func f1() {
fmt.Println("Start of f1")
defer func() {
fmt.Println("Deferred in f1")
if r := recover(); r != nil {
fmt.Println("Recovered in f1:", r)
}
}()
f2()
fmt.Println("End of f1")
}
func f2() {
fmt.Println("Start of f2")
defer func() {
fmt.Println("Deferred in f2")
}()
panic("Panic in f2")
fmt.Println("End of f2") // This line will not be executed
}
// Start of main
// Start of f1
// Start of f2
// Deferred in f2
// Deferred in f1
// Recovered in f1: Panic in f2
// End of main
引用参考文章: juejin.cn/post/684490…