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

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

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

如果程序中没有设置 recover=,=panic 会一层一层的传递直到 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