在Golang发生Panic后打印出堆栈信息
虽然用了比较长时间的Golang,但是还是有很多不懂得地方;比如,最近我才发现,原来通过recover函数拦截的err并不会返回堆栈信息,而是仅仅返回类似于“空指针错误”的信息,基本上没什么用,更没法定位到底是哪行代码发生了panic十分鸡肋;
最后经过查找网上的资料发现,可以通过runtime包获取到堆栈信息;
源代码:
在Golang发生Panic后打印出堆栈信息
对于实际的项目来说,框架都会提供recover来做业务发生panic时的拦截,保证整个服务不会因为一个业务的panic而导致整个服务直接挂掉;
同时,通常情况下框架都会记录并打出panic的堆栈信息,但是在框架之外,我们该怎么打印出来堆栈信息呢?
其实很简单通过runtime.Stack函数即可!
下面的三行代码就能返回当前Goroutine的堆栈信息:
1 2 3 4 5 6
| func getCurrentGoroutineStack() string { var buf [defaultStackSize]byte n := runtime.Stack(buf[:], false) return string(buf[:n]) }
|
下面看一个实际项目抽象出的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package main
import ( "fmt" "runtime" "sync" )
const ( defaultStackSize = 4096 )
func callPanic() { panic("test panic") }
func getCurrentGoroutineStack() string { var buf [defaultStackSize]byte n := runtime.Stack(buf[:], false) return string(buf[:n]) }
func task(arr *[]int, i int, wg *sync.WaitGroup, lock *sync.Mutex) { defer func() { if err := recover(); err != nil { fmt.Printf("[panic] err: %v\nstack: %s\n", err, getCurrentGoroutineStack()) } wg.Done() }()
if i == 500 { callPanic() }
lock.Lock() defer lock.Unlock() *arr = append(*arr, i) }
func main() { wg := sync.WaitGroup{} lock := sync.Mutex{}
arr := make([]int, 0) for i := 0; i < 10000; i++ { wg.Add(1) go task(&arr, i, &wg, &lock) } wg.Wait()
fmt.Println(len(arr)) }
|
在main函数中,会并发的创建10000个task任务;
在每个task任务中,会向arr数组的末尾添加一个 i 值;
注:Golang中内置的append函数是非线程安全的!
同时,当 i 为500时,代码模拟了业务panic的场景;
并且,为了防止单个 task 的 panic 影响到其他任务,我们在每一个 task 任务的开头都声明了defer函数,在其中使用recover对panic进行了拦截;
执行代码后输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [panic] err: test panic stack: goroutine 507 [running]: main.getCurrentGoroutineStack(...) D:/workspace/Go_Learn/app.go:20 main.task.func1(0xc000010090) D:/workspace/Go_Learn/app.go:27 +0xc5 panic(0x963180, 0x99cfa0) E:/golang/src/runtime/panic.go:969 +0x176 main.callPanic(...) D:/workspace/Go_Learn/app.go:14 main.task(0xc000004480, 0x1f4, 0xc000010090, 0xc0000100a0) D:/workspace/Go_Learn/app.go:33 +0x197 created by main.main D:/workspace/Go_Learn/app.go:48 +0x10f
9999
|
可以看到单个 task 的 panic 并不会影响到其他 task:对于添加10000个数的任务,单个任务panic后,其他的9999个任务仍然正常的执行了!
同时,我们可以很容易的定位到,Panic 来源于 D:/workspace/Go_Learn/app.go:14,即代码的第14行!
总结
对于并发的情况,对于 task 的抽象是非常重要的;
同时,对于每一个单独的并发 task,都推荐采用下面的代码来对 panic 进行拦截,防止一个 task 的 panic 影响到其他所有的 task;
并且,为每一个 task 在 panic 时打印出堆栈来直接定位问题,并保证 WaitGroup 能够正常退出;
1 2 3 4 5 6
| defer func() { if err := recover(); err != nil { fmt.Printf("[panic] err: %v\nstack: %s\n", err, getCurrentGoroutineStack()) } wg.Done() }()
|
附录
源代码: