go 的 http 服务会一直在监听某个端口,来监听客户端发过来的请求,并且要为每个请求开辟一个单独的协程。这样看起来每个协程都是独立的,不不影响。

但是,如果在某个子协程里,发生了 panic 异常,会导致服务端进程退出,从而发生严重的线上故障。

例如下面服务,将引发 panic,当然引发 panic 的还有除以 0 等操作:

package main

import (
    "time"
)

func main() {
    go func() {
        var info map[string]string
        // 对未初始化的map进行赋值,panic
        info["a"] = ""
    }()
    for {
        time.Sleep(time.Second)
    }

}

当引发 panic 时候,panic 会引发三件事情:

  1. 逆序执行当前 goroutine 的 defer 链(recover 从这里介入)。
  2. 打印错误信息和调用堆栈
  3. 调用 exit 结束整个进程

所以,我们可以在 defer 里使用 recover 函数,来阻断后面两步的执行,例如:

package main

import (
    "fmt"
    "time"
)

func main() {
    go func() {
        defer func() {
            if err := recover(); err != nil {
                // 输出:assignment to entry in nil map
                fmt.Println(err)
            }
        }()

        var info map[string]string
        // 对未初始化的map进行赋值,panic
        info["a"] = ""
    }()

    for {
        time.Sleep(time.Second)
    }
}

触发 panic 的场景总结

数组越界的时候

s := []int{1, 2, 3}
for i := 0; i <= 4; i++ {
    fmt.Println(s[i])
}

在实际开发中,也可以主动调用 panic 函数达到同样效果。

func panicFunc() {
    panic(errors.New("this is test for panic"))
}

对未初始化的map进行赋值的时候

var info map[string]string
// 对未初始化的map进行赋值,panic
info["a"] = ""

空指针的时候

最后修改:2023 年 12 月 31 日
如果觉得我的文章对你有用,请随意赞赏