Go|踩坑不完全记录之panic

根据 go spec 中对于处理 panic 的描述:

当执行一个函数 F 时,显式调用 panic 或者运行时的 panic 都会终止函数 F 的执行。此时,函数 F 中 defer 的函数会照常执行。接下来,执行函数 F 的调用者中 defer 的函数,依此执行至当前 goroutine 顶层 defer 的函数。执行完后,退出程序,并报告异常(包括 panic 函数的值)

换句话说,当 panic 出现时,会沿调用堆栈向上执行 defer 函数。这里要特别特别特别注意的是,只会执行当前 goroutine 的调用堆栈。也就是说,生成该 goroutine 的那个 goroutine 的 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
package main

import (
"fmt"
"time"
"errors"
)

func exception(){
defer fmt.Println("exception exits")
time.Sleep(time.Second/10)
panic(errors.New("throw something"))
fmt.Println("never executed")
}
func main(){
fmt.Println("test begins")
defer fmt.Println("test ends")
defer func(){
if err:=recover(); err != nil {
fmt.Println("catch a panic:", err)
}
}()
go exception()
time.Sleep(time.Second)

}

上面的代码很简单。exception 函数首先 defer 一句退出日志,然后休眠 0.1s 后,抛出一个 panic。在主函数中,我们也 defer 一句退出日志,接着 defer 一个匿名函数,用于捕获 panic。然后,利用 exception 函数创建一个 goroutine,最后休眠 1s 等待 exception 函数执行完成。

上述代码的目的是,在主 goroutine 中能够处理另一个 goroutine 的 panic。然而,运行结果却与之相悖:

1
2
3
4
5
6
7
8
9
test begins
exception exits
panic: throw something

goroutine 5 [running]:
main.exception()
/tmp/sandbox343049822/main.go:12 +0x180
created by main.main
/tmp/sandbox343049822/main.go:23 +0x100

在上面的输出中,函数 exception 执行到 panic 处时,并没有继续执行下去,而是开始执行 defer 的函数。接着,抵达当前 goroutine (id=5)的顶部后,退出程序,并抛出异常信息。此时,main goroutine 中 defer 的函数并没有执行。

一句话总结:只能在当前 goroutine 处理 panic,不能在当前 goroutine 处理其他 goroutine 产生的 panic!