七叶笔记 » golang编程 » Golang中 Defer,Panic 和 Recover的用方法

Golang中 Defer,Panic 和 Recover的用方法

背景

除了正常的流程控制之外,Go 还提供了 3 种工具来做异常的流程控制:defer,panic 和 recover。

介绍

Defer 的使用

defer 语句其实有点像 C++ 种的析构函数,但是又并不与「对象」这个概念挂钩,它更多的是在目前函数中注册清理清理函数,当函数调用结束时(有可能是异常中止),触发已注册清理函数的执行。

如这个例子:

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 动作因为异常中止了,已打开的 src 将不会被正常关闭。为了在函数退出控制流中插入我们的清理函数,可以使用 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 语句有 3 条规则:

1. defer 函数的参数将在定义时被确定

如以下这个例子:

func a() {
i := 0
defer fmt.Println(i) // 函数结束后将会打印 0
i = 250
return
}
 

2. defer 函数的调用遵循 Last In First Out,即栈顺序

如以下这个例子:

func b() {
for i := 0; i < 4; i++ { 
defer fmt.Print(i) // 注册顺序:0123,将打印 3210
}
}
 

3. defer 函数可以读取并修改函数的具名返回值

如以下这个例子:

func c() (i int) { // 返回一个 int 类型的值,变量名叫 i
defer func() { i++ }() // 读取 i,并自增
return 1
}
 

Panic 的使用

panic 是一个内置函数,用于中止正常的执行流并打印堆栈,期间将触发 defer 函数的调用。当 F() 调用 panic,它将中止 F() 的执行并触发 F() 的 defer 函数,并返回它的上一层调用者。对于上一层调用者,也相当于调用了 panic,中止正常流程并触发 defer 函数,这样一层层回溯直到顶端,此时程序就会中止。

如果发生了不可恢复的错误,可以直接使用 panic 退出程序的运行。

实际上,库函数应该避免 panic。若问题可以被屏蔽或解决,最好就让程序继续运行而不是终止整个程序。

一个可能的反例就是初始化:若某个库真的不能自己工作,且有足够的理由产生 panic,那就由它去吧。

Recover 的使用

recover 是一个内置函数,可用与程序发生 panic 的时候重新获取控制权。

当 panic 被调用后,程序将立刻终止当前函数的执行,并开始回溯 goroutine 的栈,运行 defer 函数。若回溯到达 goroutine 的顶端,程序就会终止。

我们可用内建的 recover 函数来重新取回控制权并使其恢复正常运行。

调用 recover 将停止回溯过程,并返回传入 panic 的实参。由于在回溯中只有 defer 函数中的代码在运行,因此 recover 只能在 defer 函数中才有效

如以下这个例子:

package main
import "fmt"
func f() {
// 当因为调用 g() 而导致发生 panic 的时候,将触发 f() 的 defer 函数
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)
}
func main() {
f()
fmt.Println("Returned normally from f()")
}
 

这段代码将打印:

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,将会直接 panic。

参考资料

相关文章