背景
除了正常的流程控制之外,Go 还提供了 3 种工具来做异常的流程控制:defer,panic 和 recover。
介绍
Defer 的使用
defer 语句其实有点像 C++ 种的析构函数,但是又并不与「对象」这个概念挂钩,它更多的是在目前函数中注册清理清理函数,当函数调用结束时(有可能是异常中止),触发已注册清理函数的执行。
如这个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 语句,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 函数的参数将在定义时被确定
如以下这个例子:
1
2
3
4
5
6
|
func a() {
i := 0
defer fmt.Println(i) // 函数结束后将会打印 0
i = 250
return
}
|
2. defer 函数的调用遵循 Last In First Out,即栈顺序
如以下这个例子:
1
2
3
4
5
|
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i) // 注册顺序:0123,将打印 3210
}
}
|
3. defer 函数可以读取并修改函数的具名返回值
如以下这个例子:
1
2
3
4
|
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 函数中才有效。
如以下这个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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()")
}
|
这段代码将打印:
1
2
3
4
5
6
7
8
9
10
11
12
|
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。
参考资料