Go语言中的defer、panic和recover


=Start=

defer语句用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行,它会在外围函数或者方法返回之前但是其返回值(如果有的话)计算之后执行。

如果一个函数或者方法中有多个defer语句,它们会以LIFO(Last in First out)的顺序执行

常见用法如下:

var file *os.File
var err error
if file, err = os.Open(filename); err != nil {
    log.Println("Failed to open the file", err)
    return
}
defer file.Close()

通过内置的 panic() 和 recover() 函数,Go语言提供了一套异常处理机制。

Go语言将错误和异常两者区分对待:

  • 错误是指可能出错的东西,程序需要以优雅的方式将其处理(例如,文件不能被打开)。
  • 而异常是指”不可能”发生的事情(例如,一个应该永远为true的条件在实际环境中却是false的)。

Go语言中处理错误的惯用方法是将错误以函数或者方法的最后一个返回值的形式将其返回,并总是在调用它的地方检查返回的错误值

对于”不可能发生的情况”,我们可以调用内置的 panic() 函数,该函数可以传入任何想要的值。在其他语言中,这种情况下我们可能使用一个断言(参考之前的文章:Python中的assert),但在Go语言中我们使用panic()。

在早期开发以及任何发布阶段之前,最简单同时也可能是最好用的方法是调用 panic() 函数来中断程序的执行以强制发生错误,使得该错误不会被忽略因而能够被尽快修复。

一旦开始部署程序时,任何情况下都可能发生错误,但我们应该尽一切可能避免中断程序(panic + recover)。

对于任何情况下可能运行也可能不运行的函数或者方法,如果调用了panic()函数或者调用了发生异常的函数或者方法,我们应该使用recover()以保证将异常转换成错误。理想情况下,recover()函数应该在尽可能接近于相应panic()的地方被调用,并在设置其外围函数的error返回值之前尽可能合理的将程序恢复到健康状态

==

Go语言函数的返回值也可以是任意个,如果没有,那么返回值列表的右括号后面是紧接着左大括号的。

如果函数有返回值,则函数的最后一个语句必须是一个return语句或者panic()调用。如果函数是以抛出异常结束,Go编译器会认为这个函数不需要正常返回,所以也就不需要这个return语句。

=EOF=

,

《 “Go语言中的defer、panic和recover” 》 有 11 条评论

  1. 21 | panic函数、recover函数以及defer语句 (上)
    https://time.geekbang.org/column/article/40359
    `
    问题 1:怎样让 panic 包含一个值,以及应该让它包含什么样的值?
    回答 1:
    这其实很简单,在调用panic函数时,把某个值作为参数传给该函数就可以了。由于panic函数的唯一一个参数是空接口(也就是interface{})类型的,所以从语法上讲,它可以接受任何类型的值。
    但是,我们最好传入error类型的错误值,或者其他的可以被有效序列化的值。这里的“有效序列化”指的是,可以更易读地去表示形式转换。

    问题 2:怎样施加应对 panic 的保护措施,从而避免程序崩溃?
    回答 2:
    ## recover函数的错误用法1
    panic 一旦发生,控制权就会讯速地沿着调用栈的反方向传播。所以,在panic函数调用之后的代码,根本就没有执行的机会。

    ## recover函数的错误用法2
    那如果我把调用recover函数的代码提前呢?也就是说,先调用recover函数,再调用panic函数会怎么样呢?
    这显然也是不行的,因为,如果在我们调用recover函数时未发生 panic,那么该函数就不会做任何事情,并且只会返回一个nil。
    换句话说,这样做毫无意义。

    ## recover函数的正确用法
    无论函数结束执行的原因是什么,其中的defer函数调用都会在它即将结束执行的那一刻执行。即使导致它执行结束的原因是一个 panic 也会是这样。正因为如此,我们需要联用defer语句和recover函数调用,才能够恢复一个已经发生的 panic。
    `
    22 | panic函数、recover函数以及defer语句(下)
    https://time.geekbang.org/column/article/40889

  2. Go defer 会有性能损耗,尽量不要用?
    https://github.com/EDDYCJY/blog/blob/master/golang/pkg/2019-06-16-Go-defer-loss.md
    https://segmentfault.com/a/1190000019303572
    `
    结论
    一个 defer 关键字实际上包含了不少的动作和处理,和你单纯调用一个函数一条指令是没法比的。而与对照物相比,它确确实实是有性能损耗,目前延迟调用的全部开销大约在 50ns,但 defer 所提供的作用远远大于此,你从全局来看,它的损耗非常小,并且官方还不断地在优化中。

    因此,对于 “Go defer 会有性能损耗,尽量不能用?” 这个问题,我认为该用就用,应该及时关闭就不要延迟,在 hot paths 用时一定要想清楚场景。

    补充
    最后补充上柴大的回复:“不是性能问题,defer 最大的功能是 Panic 后依然有效。如果没有 defer,Panic 后就会导致 unlock 丢失,从而导致死锁了”,非常经典。
    `

发表回复

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