「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。
Panic 是一个内置函数,它可以停止普通的控制流程并“panic”。 当函数 F 调用 panic 时,F 的执行停止,但是F 中的所有defer函数都正常执行,然后 F 返回到它的调用者。 对调用者来说,F 的行为就像是调用了panic。 该进程继续向上堆栈,直到当前 goroutine 中的所有函数都返回,此时程序崩溃。 panic可以通过直接调用panic来启动。 它们也可能是由运行时错误引起的,例如越界数组访问。
Recover 是一个内置函数,可以重新控制panic的 goroutine。 恢复仅在延迟defer函数中有用。 在正常执行期间,recover 调用将返回 nil 并且没有其他效果。 如果当前的 goroutine 正在panic,对 recovery 的调用将捕获panic,恢复正常执行。
这是一个演示panic和defer机制的示例程序:
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
函数 g 接受 int i参数,如果 i 大于 3 则恐慌,否则它使用参数 i+1 调用自身。 函数 f 延迟了一个 recover调用并打印恢复值的函数(如果它不是 nil)。 在继续阅读之前,试着想象一下这个程序的输出可能是什么。
程序会输出:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
如果我们从 f 中删除延迟函数,则panic不会恢复,并到达 goroutine 调用堆栈的顶部,从而终止程序。 这个修改后的程序将输出:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
panic PC=0x2a9cd8
[stack trace omitted]
有关panic和recover的真实示例,请参阅 Go 标准库中的 json 包。 它使用一组递归函数对接口进行编码。 如果在遍历值时发生错误,则会调用 panic 将堆栈展开到顶层函数调用,该函数调用会从 panic 中恢复并返回适当的错误值(参见 encodeState 类型的 'error' 和 'marshal' 方法 在 encode.go 中)。
Go 库中的约定是,即使包在内部使用了 panic,它的外部 API 仍然会显示显式的错误返回值。
另外一个defer的用法是:
mu.Lock()
defer mu.Unlock()
总之,defer 语句(有或没有恐慌和恢复)为控制流提供了一种不同寻常且强大的机制。 它可用于对其他编程语言中由专用结构实现的许多功能进行建模。