Golang一经面世,就因其简单易用的内置协程赢得了广大开发者的喜爱,一些之前用PHP构建的流量密集型应用也纷纷转入到Golang的阵营。只需要在函数调用前加个go关键字,这个函数就算是以协程方式运行了,真的超级简单方便。
为什么需要recover panic
如果你用过协程,那你肯定不会对recover这个函数陌生了。要知道,在协程中遇到panic,整个程序就会挂掉,即使程序重启,所造成的后果仍然会非常严重,所以goroutine通常会与recover搭配使用。
goroutine导致panic
对一些初学go的小伙伴,可能用法是这样的:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("[recover] goroutine panic: %v\n", r)
}
}()
fn()
}()
这样可以很好的解决程序因为协程中的panic挂掉,当程序panic的时候,还能打印出错误日志。
但是!错误信息往往没有堆栈信息,对我们debug帮助非常有限,他的作用也仅仅是不让程序挂掉而已。
那有没有方法可以详细打印出错误的堆栈信息呢(包含文件、行号)?答案是有!
如何recover panic
用debug.Stack() recover panic
方法1, debug.Stack(),这是标准包runtime/debug提供的一个方法,可以方便快捷地拿到当前goroutine的错误堆栈信息:
go func() {
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
log.Printf("[recover] goroutine panic: %v\n",stack)
}
}()
fn()
}()
用 runtime.Stack() recover panic
方法2: runtime.Stack,这种写法稍微繁琐一些,需要先定义接受堆栈信息的buffer。
go func() {
defer func() {
if r := recover(); r != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Printf("[recover] goroutine panic: %v\n",string(buf))
}
}()
fn()
}()
方法接收两个参数,第一个即为接收堆栈数据的buffer,第二个参数为是否打印其他 goroutine 堆栈信息。
const size = 64 << 10
意思是定义一个常量 size = 64KB
(64 << 10 == 64 * 1024
),申请一个足够大的缓冲区用于接收数据。
debug.Stack() 与 runtime.Stack() 对比
- 方法1: 写起来简介方便,但是每次调用都要重新申请内存
- 方法2:写法略微繁杂,但是buffer可以复用
结论:如果对性能要求较高,推荐方法2, 否则方法1足够应付大多场景了。