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

回首千千百百遍.还是那容那颜.
如果大家喜欢我的分享的话.可以关注我的微信公众号
念何架构之路