通常情况下,函数向其调用方报告错误的方式都是返回一个 error 类型的值。但是,当遇到致命错误的时候,很可能会使程序无法继续运行。这时,上述错误处理方式就太不适合了,Go 推荐通过调用 panic 函数来报告致命错误。

1. panic

为了报告运行期间的致命错误,Go 内建了专用函数 panic,该函数用于停止当前的控制流程并引发一个运行时恐慌。它可以接受一个任意类型的参数值,不过这个参数值的类型常常会是 string 或者 error,因为这样更容易描述运行时恐慌的详细信息。请看下面的例子:

func main()  {
outerFunc()
} func outerFunc() {
innerFunc()
} func innerFunc() {
panic(errors.New("An intended fatal error!"))
}

当调用 innerFunc 函数中的 panic 函数后,innerFunc 的执行会被停止。紧接着,流程控制权会交回调用方 outerFunc 函数。然后,outerFunc 函数的执行也将被停止。运行时恐慌就这样沿着调用栈反方向进行传播,直至到达当前 goroutine 的调用栈的最顶层。一旦达到顶层,就意味着该 goroutine 调用栈中所有函数的执行都已经被停止了,程序已经崩溃。

当然,运行时恐慌并不都是通过调用 panic 函数的方式引发的,也可以由 Go 的运行时系统来引发。例如:

myIndex := 4
ia := [3]int{1, 2, 3}
_ = ia[myIndex]

这个示例中的第 3 行代码会引发一个运行时恐慌,因为它造成了一个数组访问越界的运行时错误。这个运行时恐慌就是由 Go 的运行时系统报告的。它相当于我们显示地调用 panic 函数并传入一个 runtime.Error 类型的参数值。顺便说一句,runtime.Error 是一个接口类型,并且内嵌了 Go 内置的 error 接口类型。

显然,我们都不希望程序崩溃。那么,怎样“拦截”一个运行时恐慌呢?

2. recover

运行时恐慌一旦被引发,就会向调用方传播直至程序崩溃。Go 提供了专用于“拦截”运行时恐慌的内建函数 recover,它可以使当前的程序从恐慌状态中恢复并重新获得流程控制权。recover 函数被调用后,会返回一个 interface{} 类型的结果。如果当时的程序正处于运行时恐慌的状态,那么这个结果就会是 非 nil 的。

recover 函数应该与 defer 语句配合起来使用,例如:

defer func() {
if p := recover(); p != nil {
fmt.Printf("Recovered panic: %s\n", p)
}
}()

把此类代码放在函数体的开始处,这样可以有效防止该函数及其下层调用中的代码引发运行时恐慌。一旦发现 recover 函数的调用结果是 非 nil,就应该采取相应的措施。

值得一提的是,Go 标准库中有一种常见的用法值得我们参考。请看标准库代码包 fmt 中的 Token 函数的部分声明:

func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
defer func() {
if e := recover(); e != nil {
if se, ok := e.(scanError); ok {
err = se.err
} else {
panic(e)
}
}
}()
// 省略部分代码
}

在 Token 函数包含的 延迟函数 中,当运行时恐慌携带值的类型是 fmt.scanError 时,这个值就会被赋值给代表结果值的变量 err,否则运行时恐慌就会被重新引发。如果这个重新引发的运行时恐慌传递到了调用栈的最顶层,那么标准输出上就会打印类似这样的内容:

panic: <运行时恐慌被首次引发时携带的值的字符串形式> [recovered]
panic: <运行时恐慌被重新引发时携带的值的字符串形式>
goroutine 1 [running]:
main.func.001()
<调用栈信息> goroutine 2 [runnable:]
exit status 2

这里展现的惯用法有 2 个,如下:

  1. 可以把运行时恐慌的携带值转换为 error 类型值,并当作常规结果返回给调用方。这样既阻止了恐慌的扩散,又传递了引起恐慌的原因。
  2. 检查运行时恐慌携带值的类型,并根据类型做不同的后续动作,这样可以精确地控制程序的错误处理行为。

摘自:《Go 并发编程实战(第二版) . 郝林》

[Go] panic 和 recover的更多相关文章

  1. 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...

  2. 【GoLang】panic defer recover 深入理解

    唉,只能说C程序员可以接受go的错误设计,相比java来说这个设计真的很差劲! 我认为知乎上说的比较中肯的: 1. The key lesson, however, is that errors ar ...

  3. panic和recover的使用规则

    转自个人博客 chinazt.cc 在上一节中,我们介绍了defer的使用. 这一节中,我们温习一下panic和recover的使用规则. 在golang当中不存在tye ... catch 异常处理 ...

  4. go语言中使用defer、panic、recover处理异常

    go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...

  5. Go基础系列:defer、panic和recover

    defer关键字 defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束.即便已经panic().即便函数已经return了,也都会执行defer所推迟 ...

  6. Golang错误处理函数defer、panic、recover、errors.New介绍

    在默认情况下,当发生错误(panic)后,程序就会终止运行 如果发生错误后,可以捕获错误,并通知管理人员(邮件或者短信),程序还可以继续运行,这当然无可厚非 errors.New("错误信息 ...

  7. [日常] Go语言圣经-Panic异常,Recover捕获异常习题

    Go语言圣经-Panic异常1.当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制)2.不是所有的panic异常都来自运行时,直接调用内置的pan ...

  8. Golang异常处理-panic与recover

    Golang异常处理-panic与recover 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在程序设计中,容错是相当重要的一部分工作,在 Go中它是通过错误处理来实现的,err ...

  9. GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover

    Simple error handling primitives:        https://github.com/pkg/errors Defer, Panic, and Recover:    ...

  10. 理解Defer、Panic和Recover

    刚开始的时候理解如何使用Defer和Recover有一点怪异,尤其是使用了try/catch块的时候.有一种模式可以在Go中实现和try/catch语句块一样的效果.不过之前你需要先领会Defer.P ...

随机推荐

  1. eclipse中可以导入其它工具编写的RobotFramework脚本吗?

    在Robotframework的官方网站中,提供了非常多的编辑RF的工具.比如Ride,eclipse,sublime,notepad++等. 网上查到的资料,大部分都是Ride这个编辑工具的使用.在 ...

  2. MVC常用特性使用

    简介 在以前的文章中,我和大家讨论如何用SingalR和数据库通知来完成一个消息监控应用. 在上一篇文章中,我介绍了如何在MVC中对MongoDB进行CRUD操作. 今天,我将继续介绍一些在开发中非常 ...

  3. Paint House

    There are a row of n houses, each house can be painted with one of the k colors. The cost of paintin ...

  4. Linux内核入门(六)—— __attribute__ 机制【转】

    转自:https://blog.csdn.net/yunsongice/article/details/5538020 GNU C的一大特色(却不被初学者所知)就是__attribute__机制.__ ...

  5. python try详细说明(python的异常捕捉模块)

    #自己常用 try: pass except Exception as e: print("break for :"+str(e)) # 划重点: 1. 正常执行try情况 pri ...

  6. 解决华为手机无法输出Debug级别log的问题

    近期购入了新款的华为手机荣耀8,手感.性能.颜值都非常好.作为android开发工程师,自然会用到真机进行日常的调试.然而,这部手机并没有这么“听话“!反复尝试开启开发者选项中的设置项,依旧无法输出L ...

  7. Ibatis.Net <![CDATA[ ]]>标记学习(九)

    当Sql语句中包含特殊字符时,例如: <select id="SelectOnePerson" resultMap="PersonModel"> s ...

  8. TcxGrid 标题头高度

  9. 使用abstract关键字的使用

    package abstract1.demo01; //使用abstract关键字修饰的类叫做抽象类public abstract class Father { public static void ...

  10. Java编程的逻辑 (17) - 继承实现的基本原理

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...