Go进阶之recover

recover的中文含义为"恢复".内置函数recover()用于捕获程序中的异常.从而使用

程序回到正常流程中.

1.recover()函数:

与panic()函数一样.recover()函数也是一个内置函数.

scss 复制代码
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, recover returns nil.
//
// Prior to Go 1.21, recover would also return nil if panic is called with
// a nil argument. See [panic] for details.
func recover() any

recover()函数的返回值就是panic函数的参数.当程序产生panic时.recover()函数

就可用于消除panic.同时返回panic()函数的参数.如果程序没有发生panic.则re

cover()函数返回nil.

recover()函数必须且直接位于defer函数才有效.

示例:

go 复制代码
func RecoverTest() {
    defer func() {
       func() {
          if err := recover(); err != nil {
             fmt.Println("A")
          }
       }

    }()

    panic(nil)
    fmt.Println("B")
}

这样编译都通过不了.

2.工作流程:

出现panic后.recover可以恢复程序正常执行流程.

程序启动了两个协程.某协程中函数foo()产生了panic.并且在foo()函数中成功捕获

了该panic.程序流程将转到上游函数中继续执行.上游函数StartRoutine()感知不到

panic的发生.

小结:

recover()函数调用必须要位于defer函数中.且不能出现在另一个嵌套函数中.

recover()函数成功处理异常后.无法再次回到本函数发生panic的位置继续执行.

recover()函数可以消除本函数或收到panic.上游函数感知不到panic的发生.

3.源码剖析:

示例:

go 复制代码
package Concurrent

func recoverTest() {
    defer func() {
       recover()
    }()
}

可以看到recover()函数调用被替换成了runtime.gorecover()函数.

源码位置:src/runtime/panic.go

go 复制代码
func gorecover(argp uintptr) any {
    // Must be in a function running as part of a deferred call during the panic.
    // Must be called from the topmost function of the call
    // (the function used in the defer statement).
    // p.argp is the argument pointer of that topmost deferred function call.
    // Compare against argp reported by caller.
    // If they match, the caller is the one who can recover.
    gp := getg()
    p := gp._panic
    if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
       p.recovered = true
       return p.arg
    }
    return nil
}

1).恢复逻辑:

runtime.gorecover()函数通过协程数据结构中的_panic得到当前panic实例.如果

当前panic的状态支持recover.则给该panic实例标记recovered状态

(p.recovered=true).最后返回panic()函数的参数(p.arg).

当前执行recover()函数的defer函数是被runtime.gopanic()执行的.defer函数执

行结束以后.在runtime.gopanic()函数中会检查panic实例的recovered状态.如果

发现panic恢复.则runtime.gopanic()将结束当前panic流程.程序恢复正常流程.

2).生效条件:

通过源码的if语句可以看到需要满足四个条件才可以恢复panic.且四个条件缺一不可.

p!=nil:必须存在panic.

!p.goexit:非runtime.Goexit().

!p.recovered:panic还未被恢复.

argp==uintptr(p.argp):recover()必须被defer()直接调用.


当前协程没有产生panic时.协程结构体中panic列表为空.不满足恢复条件.

当程序运行runtime.Goexit()时也会创建一个panic实例.会标记该实例的goexit属

性为true.但该类型的panic不能被恢复.

假设函数包含多个defer函数.前面的defer通过recover()函数消除panic后.函数中

剩余的defer仍会执行.但不能再次recover().如下所示:

scss 复制代码
func foo() {
    defer func() {
       //恢复无效.因为_panic.recovered=true
       recover()
    }()
    
    defer func() {
       //标记_panic.recovered=true
       recover()
    }()
    
    panic("panic")
}

内置函数recover()没有参数.runtime.gorecover()函数却有参数.是为了限制re

cover()函数必须被defer直接调用.

runtime.gorecover()函数的参数为调用recover()函数的参数地址.通常是defer

函数的参数地址._panic实例中也保存了当前defer函数的参数地址.如果二者一致.说

明recover()被defer函数直接调用.

3).设计思路:

如果recover函数不在defer函数中.那么recover()函数可能出现在panic()之前.也

可能出现在panic()之后.出现在panic之前.因为找不到panic实例无法生效,出现在

panic()之后.没有机会执行.所以recover()函数必须在defer函数中生效.

回首千千百百遍.还是那容那颜.





如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

相关推荐
程序员布吉岛7 小时前
写了 10 年 MyBatis,一直以为“去 XML”=写注解,直到看到了这个项目
后端
却尘7 小时前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
茶杯梦轩7 小时前
从零起步学习Redis || 第七章:Redis持久化方案的实现及底层原理解析(RDB快照与AOF日志)
redis·后端
QZQ541887 小时前
重构即时IM项目13:优化消息通路(下)
后端
柠檬味拥抱7 小时前
揭秘Cookie操纵:深入解析模拟登录与维持会话技巧
后端
不想打工的码农7 小时前
MyBatis-Plus多数据源实战:被DBA追着改配置后,我肝出这份避坑指南(附动态切换源码)
java·后端
ZeroTaboo7 小时前
rmx:给 Windows 换一个能用的删除
前端·后端
Coder_Boy_7 小时前
Deeplearning4j+ Spring Boot 电商用户复购预测案例
java·人工智能·spring boot·后端·spring
Victory_orsh7 小时前
AI雇佣人类,智能奴役肉体
后端