背景
除了正常的流程控制之外,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。
参考资料