panic
在深服气的口试题中出了这样一道题:
package mainimport "fmt"func main() { defer func() { fmt.Println(1) }() defer func() { fmt.Println(2) }() panic(3) defer func() { fmt.Println(4) }()}
过后我给出的答案是输入panic: 3
,那么正确答案是什么呢?
正确答案为
21 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}