# panic
panic 是一个内建函数,用于引发程序的运行时错误(runtime error)或异常(exception)。
panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer。
当出现无法恢复的错误或不符合预期的情况时,你可以使用 panic 函数来中止当前的程序执行流程。
- 假如函数 F 有 panic 语句执行,会终止 panic 语句之后要执行的代码
- 在 panic 所在函数 F 内如果存在要执行的 defer 函数列表,按照 defer 的先进后出
- 返回函数 F 的调用者 G,在 G 中,调用函数 F 语句之后的代码不会执行,假如函数 G 中存在要执行的 defer 函数列表,按照 defer 的逆序执行
func G(i int) {
defer func() {
fmt.Println("G2 panic 2")
}()
defer func() {
fmt.Println("G2 panic 1")
}()
F(i)
}
func F(i int) {
defer func() {
// panic 执行之后,这个代码会被调用
fmt.Println("panic 执行之后可以执行2")
}()
defer func() {
// panic 执行之后,这个代码会被调用
fmt.Println("panic 执行之后可以执行1")
}()
if i == 1 {
panic("Something went wrong!")
}
fmt.Println("test 没有发生错误") // 当 panic执行之后,这行代码不会执行
}
panic 执行之后可以执行1
panic 执行之后可以执行2
G2 panic 1
G2 panic 2
panic: Something went wrong!
Process finished with the exit code 2
# recover
在 Go 编程语言中,recover 是一个内建函数,recover 函数用于捕获并处理 panic 引发的错误。
它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
处理之后,程序会正常运行。
package main
import "fmt"
func main() {
G(1)
fmt.Println("在 main 方法执行 test 方法之后,打印")
}
func G(i int) {
defer func() {
if err := recover(); err != nil {
fmt.Println("发生了 panic:", err)
}
}()
F(i)
}
func F(i int) {
defer func() {
// panic 执行之后,这个代码会被调用
fmt.Println("panic 执行之后可以执行2")
}()
defer func() {
// panic 执行之后,这个代码会被调用
fmt.Println("panic 执行之后可以执行1")
}()
if i == 1 {
panic("Something went wrong!")
}
fmt.Println("test 没有发生错误") // 当 panic执行之后,这行代码不会执行
}
panic 执行之后可以执行1
panic 执行之后可以执行2
发生了 panic: Something went wrong!
在 main 方法执行 test 方法之后,打印
Process finished with the exit code 0
# 总结
panic 关键字在 Go 语言的源代码是由数据结构 runtime._panic 表示的。每当我们调用 panic 都会创建一个如下所示的数据结构存储相关信息:
argp是指向defer调用时参数的指针;arg是调用panic时传入的参数;link指向了更早调用的runtime._panic(opens new window) 结构;recovered表示当前runtime._panic(opens new window) 是否被recover恢复;aborted表示当前的panic是否被强行终止;
type _panic struct {
argp unsafe.Pointer
arg interface{}
link *_panic
recovered bool
aborted bool
pc uintptr
sp unsafe.Pointer
goexit bool
}
程序崩溃和恢复的过程:
编译器会负责做转换关键字的工作;
- 将
panic和recover分别转换成runtime.gopanic和runtime.gorecover - 将
defer转换成runtime.deferproc函数 - 在调用
defer的函数末尾调用runtime.deferreturn
- 将
在运行过程中遇到
runtime.gopanic方法时,会从 Goroutine 的链表依次取出runtime._defer结构体并执行;如果调用延迟执行函数时遇到了
runtime.gorecover就会将_panic.recovered标记成 true 并返回panic的参数;如果没有遇到
runtime.gorecover就会依次遍历所有的runtime._defer,并在最后调用runtime.fatalpanic中止程序、打印panic的参数并返回错误码 2;