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
}

你可能感兴趣的:(go)