Go 的异常处理:defer,panic,recover
Go 语言没有提供传统的 try-catch
这样的异常处理机制,所有的错误基本上都是靠程序返回值判断,但是写程序难免会遇到类似忘记
判断返回值越界访问,进而程序崩溃。
但是 Go 提供了 panic
机制,程序崩溃时会抛出 panic
,在程序中可以通过 recover
捕捉抛出的 panic
内容(还可以获取堆栈),
recover
之后程序会继续执行不会崩溃。
如果程序中没有设置 recover
, panic
会一层一层的传递直到 main 函数(如果 main 没有处理,最后才崩溃)。
这是一个非常实用的功能,试想一个 Web 服务器的某一条 HTTP 处理崩溃了,导致了整个服务器崩溃显然不是我们想要的,我们当然不希 望一个 API 请求执行 crash 影响整个 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
搭配只用,具体使用方法自行看文档,就不赘述了。使用时有几点要注意:
panic
会根据函数的调用顺序逐层传递;- 被
recover
捕获之后,则定制传递,当前函数自动退出(defer 本来也就是在函数执行完毕时才会调用),其他程序自动运行; - 未被
recover
捕捉,一直会抛出到main
函数,然后进程 crash;
- 被
panic
仅限于 当前协程(routine),子协程抛出panic
,父协程无法捕获;- 虽然子协程
panic
父协程无法捕获,但子协程的panic
会导致父协程 crash(所以子协程最好捕获panic
),想要父协程获 取子协程的panic
信息,可以在子协程recover
之后用 channel 传递出去;
defer
使用注意事项(官方提供):
defer
函数参数会在defer
声明的时候求值(笔者:可以简单理解成 C 语言中的宏);func a() { i := 0 defer fmt.Println(i) i++ return } // i 的值是 0 而不是 1
defer
函数调用顺序的「后进先出」,即最先声明的最后调用;defer
函数可以读写函数的命名返回值;func c() (i int) { defer func() { i++ }() return 1 } // 返回值中的 i 值为 2,而不是 1