Defer, Panic, and Recover

原文链接http://blog.golang.org/defer-panic-and-recover

defer

在一些资源相关的操作中,由于逻辑的复杂,资源的关闭操作可能会被遗漏。采用defer可以比较简单的解决这个问题。比如如下的代码中:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

如果os.Create()失败了,将会导致source file没有关闭的情况。如果你使用defer,就可以很方便的放置你的关闭代码。即使逻辑复杂也可以比较优雅的实现。

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

在defer的使用过程中,需要注意几条规则,就能防止误用:

(1) A deferred function’s arguments are evaluated when the defer statement is evaluated.(defer函数的参数在函数表达式解析的时候就求值)

在下面的例子中,i在解析defer函数的时候就求值,所以最后会输出0:

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

(2) Deferred function calls are executed in Last In First Out order after_the surrounding function returns.(defer函数遵循先入后出的规则,因为其本身是压栈的方式)

下面的代码将输出“3210”

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

(3) Deferred functions may read and assign to the returning function’s named return values.(defer函数可以用来操作返回值)

下面的代码将返回2

func c() (i int) {
    defer func() { i++ }()
    return 1
}

这个特点非常方便的用来修改返回的error

上面的几条规则非常简单,还有一个比较容易出错的点,本质和第三条一致。在原博的基础上我又添加了一条:

(4) defer函数中除参数以外(参数以值传递方式入栈),其余变量都是引用的形式,与一般函数一致

所以下面的代码会诡异的输出“4444”

package main

import "fmt"

func main() {
    for i := 0; i < 4; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}

panic

panic是一个产生运行时恐慌的内建函数,你可以手动调用panic来触发,也有可能是运行时错误导致的自动panic。
需要注意的几点是:

  • panic产生后每个递归的defer的方法依次出栈执行。panic从子程序往父程序传递,直到当前goroutine返回。
  • go库函数的惯例一般都是API内部panic由recover转化为显式error返回

recover

recover是内奸函数用来捕捉panic,并阻止了panic的逐级上传,和字面意思蛮像的其实。

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

上面的代码将会输出:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

如果我们将f函数中的recover方法移除,goroutine将会由此结束,从而输出:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4

panic PC=0x2a9cd8
[stack trace omitted]

json包中有很多利用panic和recover的例子,遇到不符合json规则的文本,其内部会触发panic,并在高层代码中捕捉panic将其改成合适的error返回值。(参与代码decode.go

含有一些常见的defer的使用案例,如锁的加锁解锁:

mu.Lock()
defer mu.Unlock()

输出footer:

printHeader()
defer printFooter()

凡此种种,碰到问题再讨论吧…

by Andrew Gerrand(translator: xiaohu)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值