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函数中生效.

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





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

念何架构之路

相关推荐
demo007x12 分钟前
Docling 文档转换以及技术架构分析
前端·后端·程序员
袋鱼不重2 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
用户8356290780512 小时前
使用 Python 操作 Word 内容控件
后端·python
像我这样帅的人丶你还2 小时前
啥? 前端也要会干Java?🛵🛵🛵
后端
Hommy882 小时前
【剪映小助手】添加贴纸接口(Add Sticker)
后端·github·剪映小助手·视频剪辑自动化·剪映api
CaffeinePro2 小时前
FastAPI响应处理:返回值、状态码、响应头与异常标准化与案例解析
后端
HuanYu3 小时前
PageHelper分页的原理
后端
于先生吖3 小时前
SpringBoot对接大模型开发AI命理测算系统:八字排盘与AI解析接口源码全解
人工智能·spring boot·后端
张不才3 小时前
一个静默吞数据的时间戳陷阱
后端
李少兄3 小时前
从原理到实战:Spring IoC/DI 核心知识体系与高频面试题全解
java·后端·spring