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 能够组成链表。

程序解体

  1. 创立新的 runtime._panic并增加到所在 Goroutine 的 _panic 链表的最后面;
  2. 在循环中一直从以后 Goroutine 的 _defer 中链表获取 _defer并调用 freedefer运行提早调用函数;
  3. 调用 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}