panic
在深信服的笔试题中出了这样一道题:
package main
import "fmt"
func main() {
defer func() {
fmt.Println(1)
}()
defer func() {
fmt.Println(2)
}()
panic(3)
defer func() {
fmt.Println(4)
}()
}
当时我给出的答案是输出panic: 3
,那么正确答案是什么呢?
正确答案为
2
1
panic: 3
Go程序在panic
时并不会导致程序异常退出,而是会终止当前函数的正常执行,执行defer并逐级返回。(即:panic在退出协程前会执行所有已注册的defer(panic
只会触发当前 Goroutine 的延迟函数调用))
要点
panic
能够改变程序的控制流,调用panic
后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的defer
;recover
可以中止panic
造成的程序崩溃。它是一个只能在defer
中发挥作用的函数,在其他作用域中调用不会发挥作用;panic
只会触发当前 Goroutine 的defer
;recover
只有在defer
中调用才会生效;panic
允许在defer
中嵌套多次调用;recover
只有在发生panic
之后调用才会生效
底层原理
数据结构
type _panic struct {
argp unsafe.Pointer // 指向 defer 调用时参数的指针
arg interface{} // 调用 panic 时传入的参数
link *_panic // 指向了更早调用的 runtime._panic 结构
recovered bool // 表示当前 runtime._panic 是否被 recover 恢复
aborted bool // 表示当前的 panic 是否被强行终止
pc uintptr
sp unsafe.Pointer
goexit bool
}
panic
函数可以被连续多次调用,它们之间通过 link
可以组成链表。
程序崩溃
- 创建新的
runtime._panic
并添加到所在 Goroutine 的_panic
链表的最前面; - 在循环中不断从当前 Goroutine 的
_defer
中链表获取_defer
并调用freedefer
运行延迟调用函数; - 调用
fatalpanic
中止整个程序;
func gopanic(e any) {
gp := getg()
...
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
...
for {
d := gp._defer
if d == nil {
break
}
// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
// take defer off list. An earlier panic will not continue running, but we will make sure below that an
// earlier Goexit does continue running.
if d.started {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
if !d.openDefer {
// For open-coded defers, we need to process the
// defer again, in case there are any other defers
// to call in the frame (not including the defer
// call that caused the panic).
d.fn = nil
gp._defer = d.link
freedefer(d)
continue
}
}
...
fatalpanic(gp._panic)
*(*int)(nil) = 0
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。