Go 的异常处理:defer, panic, recover

Go 语言没有提供传统的 try-catch 这样的异常处理机制,所有的错误基本上都是靠程序返回值判断, 但是写程序难免会遇到类似忘记判断返回值越界访问,进而程序崩溃。

但是 Go 提供了 panic 机制,程序崩溃时会抛出 panic ,在程序中可以通过 recover 捕捉抛出的 panic 内容(还可以获取堆栈), recover 之后程序会继续执行不会崩溃。

如果程序中没有设置 recoverpanic 会一层一层的传递直到 main 函数(如果 main 没有处理,最后才崩溃)。

这是一个非常实用的功能,试想一个 Web 服务器的某一条 HTTP 处理崩溃了,导致了整个服务器崩溃显然不是我们想要的,我们希望一个 API 请求失败丝毫不影响整个 Web 服务器。 当然也可以在服务器外部定时检查服务器进程是否存在,发现不存在的时候自动重启。但是像游戏服务器某个协议的处理 Bug(升级装备、领取奖励)导致崩溃进而引发内存中的数据没有及时回写到存储 DB 中,而且即便是外部进程实时拉起服务器进程,服务初始化可能也有会耗时。

Go 的 panic 机制,完美的解决了这个问题,比如在 Go 的 net/http 库中已经加上了 =panic=:

// go/net/http.go
func (c *conn) serve(ctx context.Context) {
    defer func() {
        if err := recover(); err != nil && err != ErrAbortHandler {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        }
        if !c.hijacked() {
            c.close()
            c.setState(c.rwc, StateClosed)
        }
    }()

    // ...
    for {
        // 当 ServeHTTP(回掉业务中的 ServeHTTP) 出现崩溃时,会被前面的 recover() 捕获,并获取将堆栈信息输出到日志中
        serverHandler{c.server}.ServeHTTP(w, w.req)
    }
}

recover() 必须和 defer 搭配只用,具体使用方法自行看文档,就不赘述了。使用时有几点要注意:


defer 使用注意事项(官方提供):

  1. defer 函数参数会在 defer 声明的时候求值(笔者:可以简单理解成 C 语言中的宏);

    func a() {
        i := 0
        defer fmt.Println(i)
        i++
        return
    }
    // i 的值是 0 而不是 1
    
  2. defer 函数调用顺序的「后进先出」,即最先声明的最后调用;
  3. defer 函数可以读写函数的命名返回值;

    func c() (i int) {
        defer func() { i++ }()
        return 1
    }
    // 返回值中的 i 值为 2,而不是 1
    

Date: 2018-03-01 17:50:00

Author: JerryZhang