为什么拨打defer func() { recover() }()
成功恢复恐慌goroutine,但拨打defer recover()
却没有?
作为一个简约的例子,这段代码并不恐慌
package main
func main() {
defer func() { recover() }()
panic("panic")
}
但是,用recover直接替换匿名函数恐慌
package main
func main() {
defer recover()
panic("panic")
}
答案 0 :(得分:10)
引自内置函数recover()
的文档:
如果将称为外部延迟函数,则不会停止恐慌序列。
在你的第二种情况下,recover()
本身就是延迟函数,显然recover()
不会调用自身。所以这不会阻止恐慌序列。
如果recover()
本身会调用recover()
,它会停止恐慌序列(但为什么会这样做?)。
另一个有趣的例子:
以下代码也没有恐慌(在Go Playground上尝试):
package main
func main() {
var recover = func() { recover() }
defer recover()
panic("panic")
}
这里发生的是我们创建一个函数类型的recover
变量,它具有一个调用内置recover()
函数的匿名函数的值。我们指定调用recover
变量的值作为延迟函数,因此从中调用内置recover()
会停止panicing序列。
答案 1 :(得分:7)
Handling panic部分提及
两个内置函数
panic
和recover
有助于报告和处理运行时恐慌
recover
功能允许程序管理恐慌goroutine的行为。假设函数
G
推迟调用D
的函数recover
,并且在同一个goroutine中的函数中发生panic
G
正在执行。当延迟函数的运行达到
D
时,D
调用recover
的返回值将是传递给恐慌调用的值。
如果D正常返回,没有开始新的恐慌,恐慌序列就会停止。
这表明recover
意味着在延迟函数中调用,而不是直接调用
当它发生恐慌时,“延迟功能”不能是内置的recover()
,而是 defer statement 中指定的功能。
DeferStmt = "defer" Expression .
表达式必须是函数或方法调用;它不能用括号括起来 内置函数的调用受限于expression statements 。
除了特定的内置函数,函数和方法调用和接收操作可以出现在语句上下文中。
答案 2 :(得分:0)
观察到,这里的真正问题是defer
的设计,因此答案应该是这样。
为解决这个问题,defer
目前需要从lambda中获取一层嵌套堆栈,并且运行时使用此约束的特定副作用来确定recover()
是否返回nil是否。
这是一个例子:
func b() {
defer func() { if recover() != nil { fmt.Printf("bad") } }()
}
func a() {
defer func() {
b()
if recover() != nil {
fmt.Printf("good")
}
}()
panic("error")
}
recover()
中的b()
应该返回nil。
我认为,更好的选择是说defer
将函数BODY或块作用域(而不是函数调用)作为其参数。此时,panic
和recover()
的返回值可以绑定到特定的堆栈框架,并且任何内部堆栈框架都将具有nil
临时上下文。因此,它看起来像这样:
func b() {
defer { if recover() != nil { fmt.Printf("bad") } }
}
func a() {
defer {
b()
if recover() != nil {
fmt.Printf("good")
}
}
panic("error")
}
在这一点上,很明显a()
处于恐慌状态,但b()
却不处于恐慌状态,并且诸如“处于延迟的lambda的第一个堆栈帧”之类的任何副作用都没有正确实现运行时所必需的。
因此,这里有悖于事实:之所以无法正常工作的原因是go语言中defer
关键字的设计错误,这是通过使用非显而易见的关键字解决的实施会详细说明副作用,然后将其编成代码。