虽然用了比较长时间的Golang,但是还是有很多不懂得地方;比如,最近我才发现,原来通过recover函数拦截的err并不会返回堆栈信息,而是仅仅返回类似于“空指针错误”的信息,基本上没什么用,更没法定位到底是哪行代码发生了panic十分鸡肋;
最后经过查找网上的资料发现,可以通过runtime包获取到堆栈信息;
源代码:
recoverpanicpanic
panic
runtime.Stack
下面的三行代码就能返回当前Goroutine的堆栈信息:
// getCurrentGoroutineStack 获取当前Goroutine的调用栈,便于排查panic异常
func getCurrentGoroutineStack() string {
var buf [defaultStackSize]byte
n := runtime.Stack(buf[:], false)
return string(buf[:n])
}
下面看一个实际项目抽象出的例子:
package main
import (
"fmt"
"runtime"
"sync"
)
const (
defaultStackSize = 4096
)
func callPanic() {
panic("test panic")
}
// getCurrentGoroutineStack 获取当前Goroutine的调用栈,便于排查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))
}
task
task
append
同时,当 i 为500时,代码模拟了业务panic的场景;
recover
执行代码后输出:
[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个任务仍然正常的执行了!
D:/workspace/Go_Learn/app.go:14
总结
对于并发的情况,对于 task 的抽象是非常重要的;
同时,对于每一个单独的并发 task,都推荐采用下面的代码来对 panic 进行拦截,防止一个 task 的 panic 影响到其他所有的 task;
并且,为每一个 task 在 panic 时打印出堆栈来直接定位问题,并保证 WaitGroup 能够正常退出;
defer func() {
if err := recover(); err != nil {
fmt.Printf("[panic] err: %v\nstack: %s\n", err, getCurrentGoroutineStack())
}
wg.Done()
}()
源代码: