共计 1593 个字符,预计需要花费 4 分钟才能阅读完成。
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
}
正文完