golang中Defer、Panic、Recover的用法

因为golang没有try……catch的用法,但是可以通过defer + recover来实现,但是要先明确defer与return之间的执行顺序。

defer

先看defer的定义(参考tour go)。

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
}

这段代码可以奏效,但是存在bug。如果调用os.Create失败了,函数还没有释放资源就返回了。如果函数更复杂,潜在的bug就会更多了。通过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:defer修饰函数的变量在defer被声明时就确定了,例如下面的代码

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

代码最终会输出 0 而不是 1 。这是因为在声明defer修饰的 fmt.Println(i)时,变量i的值就确定了,这时i的值还是 0 。

2:多个defer的定义与执行类似于栈的操作:先进后出,最先定义的最后执行。

package main

import "fmt"

func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}

	fmt.Println("done")
}

这段代码的执行结果:

counting
done
9
8
7
6
5
4
3
2
1
0

3:defer修饰的函数可以读取并且赋值给函数的返回值(这里有点翻译的不通顺,先看代码吧)

package main

import "fmt"

func main() {	
	fmt.Println(c())
}

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

代码最终输出结果是 2 。defer修饰的函数可以读取返回的 i 值并且给他赋新值。

Panic

Panic是一个内置函数,可以打断正常流程。例如一个函数F调用了panic,F停止执行,F所有的defer函数会正常执行,defer函数都执行完后,F函数返回给它的调用者。对于F函数的调用者来说,此时,F函数的返回相当于又调用了一次panic。F函数的调用者就像F函数一样执行相同的逻辑,直到这个goroutine栈里所有的函数都panic返回了,进而整个程序终止了(如果goroutine没有对panic通过defer进行捕捉)。

Panic可以调用panic函数直接触发,也会因为一些其他runtime error触发,例如数组越界等等。

Recover

Recover也是一个内置函数,可以重新获取一个panic的goroutine的控制权。Recover只有和defer结合起来,内置在defer函数中,才会起作用。如果函数正常执行,调用recover()返回的是nil,如果当前goroutine处于panic,调用recover()就可以捕捉到panic时设定的value,然后继续正常的执行流程。

下面是panic和defer机制的一个示例代码,

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.

未完待续……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值