Posted in

如何优雅地Recover协程中的panic

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 = 64KB64 << 10 == 64 * 1024),申请一个足够大的缓冲区用于接收数据。

debug.Stack() 与 runtime.Stack() 对比

  • 方法1: 写起来简介方便,但是每次调用都要重新申请内存
  • 方法2:写法略微繁杂,但是buffer可以复用

结论:如果对性能要求较高,推荐方法2, 否则方法1足够应付大多场景了。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注