Panic,堆栈跟踪以及如何恢复【最佳实践】(译文)

xiayuguo · · 3106 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

作者:Stefan Nilsson

原文网址:yourbasic.org/golang/reco…

Panic 是 Go 中的一个异常

Panics 类似于 C++ 和 Java 异常,但仅适用于运行时错误,例如跟随一个 nil 指针或试图对数组访问超出范围的索引。为了表示诸如文件结束之类的事件,Go 程序使用内置 error 类型。有关错误的更多信息,请参见 错误处理最佳实践3种创建错误的简单方法

Panic 停止 goroutine 的正常执行

  • 程序出现 panic 时,它将立即开始展开调用堆栈。
  • 一直持续到程序崩溃并打印堆栈跟踪,
  • 或直到调用内置的恢复功能。

panic 是由运行时错误或对内置函数 panic 的显式调用引起的。

堆栈跟踪记录

堆栈跟踪记录 —— 所有活动堆栈帧的报告 —— 通常在 panic 发生时将其打印到控制台。堆栈跟踪对于调试非常有用:

  • 您不仅可以看到错误发生的地方,
  • 而且可以看到程序是如何到达这个地方的。

解释堆栈跟踪

这是一个堆栈跟踪的示例:

goroutine 11 [running]:
testing.tRunner.func1(0xc420092690)
    /usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x53f820, 0x594da0)
    /usr/local/go/src/runtime/panic.go:491 +0x283
github.com/yourbasic/bit.(*Set).Max(0xc42000a940, 0x0)
    ../src/github.com/bit/set_math_bits.go:137 +0x89
github.com/yourbasic/bit.TestMax(0xc420092690)
    ../src/github.com/bit/set_test.go:165 +0x337
testing.tRunner(0xc420092690, 0x57f5e8)
    /usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
    /usr/local/go/src/testing/testing.go:789 +0x2de
复制代码

可以从下至上阅读:

  • testing.(*T).Run 调用了 testing.tRunner,
  • testing.tRunner 调用了 bit.TestMax,
  • bit.TestMax 调用了 bit.(*Set).Max,
  • bit.(*Set).Max 调用了 panic,
  • panic 调用了 testing.tRunner.func1

缩进的行显示了调用该函数的源文件和行号。十六进制数字表示参数值,包括指针和内部数据结构的值。Go 中的堆栈跟踪 具有更多详细信息。

打印并记录堆栈跟踪

要打印当前 goroutine 的堆栈跟踪,请使用包 runtime/debug 中的debug.PrintStack

您还可以通过调用 runtime.Stack 以编程方式检查当前的堆栈跟踪

详细程度

变量 GOTRACEBACK 控制 Go 程序失败时生成的输出量。

  • GOTRACEBACK = none 完全忽略 goroutine 堆栈跟踪。
  • GOTRACEBACK = single(默认)为当前goroutine打印堆栈跟踪, 从而消除运行时系统内部的功能。如果没有当前goroutine或故障是运行时内部的,则故障会打印所有goroutine的堆栈跟踪。
  • GOTRACEBACK = all 为所有用户创建的goroutine添加堆栈跟踪。
  • GOTRACEBACK = system 与其他系统一样,但是为运行时函数添加了堆栈框架,并显示了运行时在内部创建的 goroutine。

恢复和捕获 Panic

内置的 recover 函数可用于重新获得对异常程序的控制并恢复正常执行。

  • 调用 recover 将停止展开并返回传递给 panic 的参数。
  • 如果 goroutine 没有异常,则恢复将返回 nil。

因为展开时运行的唯一代码是在 defer 函数内部,所以 recover 仅在此类函数内部有用。

Panic 处理程序示例

func main() {
	n := foo()
	fmt.Println("main received", n)
}

func foo() int {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	m := 1
	panic("foo: fail")
	m = 2
	return m
}
复制代码
foo: fail
main received 0
复制代码

由于 panic 是在 foo 返回值之前发生的,因此 n 仍然具有其初始零值。

返回值

要在发生 panic 时返回值,必须使用命名返回值。

func main() {
	n := foo()
	fmt.Println("main received", n)
}

func foo() (m int) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			m = 2
		}
	}()
	m = 1
	panic("foo: fail")
	m = 3
	return m
}
复制代码
foo: fail
main received 2
复制代码

测试 Panic(实用功能)

在此示例中,我们使用反射来检查接口变量列表是否具有与给定函数的参数相对应的类型。如果是这样,我们使用这些参数调用该函数以检查是否有 panic。

// Panics tells if function f panics with parameters p.
func Panics(f interface{}, p ...interface{}) bool {
	fv := reflect.ValueOf(f)
	ft := reflect.TypeOf(f)
	if ft.NumIn() != len(p) {
		panic("wrong argument count")
	}
	pv := make([]reflect.Value, len(p))
	for i, v := range p {
		if reflect.TypeOf(v) != ft.In(i) {
			panic("wrong argument type")
		}
		pv[i] = reflect.ValueOf(v)
	}
	return call(fv, pv)
}

func call(fv reflect.Value, pv []reflect.Value) (b bool) {
	defer func() {
		if err := recover(); err != nil {
			b = true
		}
	}()
	fv.Call(pv)
	return
}
复制代码

扫描下方二维码,关注Feed, 定期推送最新随笔

公众号 Feed 二维码

有疑问加站长微信联系(非本文作者)

本文来自:掘金

感谢作者:xiayuguo

查看原文:Panic,堆栈跟踪以及如何恢复【最佳实践】(译文)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3106 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传