defer, panic 与 recover

这小节我们要介绍Go里面的函数执行流程。

defer

Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。 当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。 特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。 如下代码所示,我们一般写打开一个资源是这样操作的:

func ReadWrite() bool {
    file.Open("file")
    // 做一些工作
    if failureX {
        file.Close()
        return false
    }

    if failureY {
        file.Close()
        return false
    }

    file.Close()
    return true
}

我们看到上面有很多重复的代码,Go的defer有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。 在defer后指定的函数会在函数退出前调用。

func ReadWrite() bool {
    file.Open("file")
    defer file.Close()

    if failureX {
        return false
    }

    if failureY {
        return false
    }

    return true
}

如果有很多调用defer,那么defer是采用后进先出模式,所以如下代码会输出4 3 2 1 0

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

Panic和Recover

Go没有像Java那样的异常机制,它不能抛出异常,而是使用了panicrecover机制。 一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。 这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢?

Panic

是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。 当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。 在调用的地方,F的行为就像调用了panic。 这一过程继续向上,直到发生panicgoroutine中所有调用的函数返回,此时程序退出。 恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。

Recover

是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。 recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。 如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。

下面这个函数演示了如何在过程中使用panic

var user = os.Getenv("USER")

func init() {
    if user == "" {
        panic("no value for $USER")
    }
}

下面这个函数检查作为其参数的函数在执行时是否会产生panic

func throwsPanic(f func()) (b bool) {

    defer func() {
        if x := recover(); x != nil {
            b = true
        }
    }()

    f() //执行函数f,如果f中出现了panic,那么就可以恢复回来

    return
}

进阶

下面看一个示例:

package main

import (
    "fmt"
)

func f1() {
    defer func() {
        if x := recover(); x != nil {
            fmt.Println("Recover:", x)
        }
    }()
    fmt.Println("f1")
    defer func() {
        fmt.Println("defer before f2")
    }()
    f2()
    defer func() {
        fmt.Println("defer after f2")
    }()
    fmt.Println("after f2")
}

func f2() {
    fmt.Println("f2")
    panic("f2")
}

func main() {
    f1()
}

这个Demo最终的输出会是什么呢?

要弄清楚输出的内容及顺序,就得了解defer, panicrecover之间的关系。这里先给出上面Demo输出的结果:

f1
f2
defer before f2
Recover: f2

根据上面结果可以得知:

  1. panic后的所有代码都不会再执行了,即使通过recover进行了恢复。
  2. recover之前,已经defer过的代码块会按倒序进行执行(也就是panic后的defer也不会执行)。
  3. recover之后,函数就执行了return操作,也就是不会再执行当前函数里的代码了。

results matching ""

    No results matching ""