乐趣区

关于go:panic

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

程序解体

  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
}
退出移动版