为什么`推迟恢复()`不会引起恐慌?

时间:2015-04-08 14:51:01

标签: go deferred-execution

为什么拨打defer func() { recover() }()成功恢复恐慌goroutine,但拨打defer recover()却没有?

作为一个简约的例子,这段代码并不恐慌

package main

func main() {
    defer func() { recover() }()
    panic("panic")
}

但是,用recover直接替换匿名函数恐慌

package main

func main() {
    defer recover()
    panic("panic")
}

3 个答案:

答案 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部分提及

  

两个内置函数panicrecover有助于报告和处理运行时恐慌

     

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或块作用域(而不是函数调用)作为其参数。此时,panicrecover()的返回值可以绑定到特定的堆栈框架,并且任何内部堆栈框架都将具有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关键字的设计错误,这是通过使用非显而易见的关键字解决的实施会详细说明副作用,然后将其编成代码。