延迟语句如何配合恢复语句
Go 语言被诟病多次的就是它的 error,实际项目里经常出现各种 error 满天飞,正常的代码逻辑里有很多 error 处理的代码块。函数总是会返回一个 error,留给调用者处理;而如果是致命的错误,比如程序执行初始化的时候出问题,最好直接 panic 掉,避免上线运行后出更大的问题。
有些时候,需要从异常中恢复。比如服务器程序遇到严重问题,产生了 panic,这时至少可以在程序崩溃前做一些 “扫尾工作”,比如关闭客户端的连接,防止客户端一直等待等;并且单个请求导致的 panic,也不应该影响整个服务器程序的运行。
panic 会停掉当前正在执行的程序,而不只是当前线程。在这之前,它会有序地执行完当前线程 defer 列表里的语句, 其他协程里定义的 defer 语句不作保证。 所以在 defer 里定义一个 recover 语句,防止程序直接挂掉,就可以起到类似 Java 里 try...catch 的效果。
注意,recover () 函数只在 defer 的函数中直接调用才有效。例如:
func deferRecover() {
defer fmt.Println("defer main")
var user = os.Getenv("USER_")
go func() {
defer func() {
fmt.Println("defer caller")
if err := recover(); err != nil {
fmt.Println("recover success. err: ", err)
}
}()
func() {
defer func() {
fmt.Println("defer here")
}()
if user == "" {
panic("should set user env.")
}
// 此处不会执行
fmt.Println("after panic")
}()
}()
time.Sleep(100)
fmt.Println("end of main function")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
程序的执行结果:
end of main function
defer main
defer here
defer caller
recover success. err: should set user env.
2
3
4
5
代码中的 panic 最终会被 recover 捕获到。这样的处理方式在一个 http server 的主流程常常会被用到。一次偶然的请求可能会触发某个 bug,这时用 recover 捕获 panic,稳住主流程,不影响其他请求。
同样,我们再来看几个延伸示例。这些例子都与 recover () 函数的调用位置有关。
考虑以下写法,程序是否能正确 recover 吗?如果不能,原因是什么:
func main() {
defer f()
panic(404)
}
func f() {
if e := recover(); e != nil {
fmt.Println("recover")
return
}
}
2
3
4
5
6
7
8
9
10
11
能。在 defer 的函数中调用,生效。
func main() {
recover()
panic(404)
}
2
3
4
不能。直接调用 recover,返回 nil。
func main() {
defer recover()
panic(404)
}
2
3
4
不能。要在 defer 函数里调用 recover。
func main() {
defer func() {
if e := recover(); e != nil {
fmt.Println("recover")
}
}()
panic(404)
}
2
3
4
5
6
7
8
能。在 defer 的函数中调用,生效。
func main() {
defer func() {
recover()
}()
panic(404)
}
2
3
4
5
6
能。在 defer 的函数中调用,生效。
func main() {
defer func() {
defer func() {
recover()
}()
}()
panic(404)
}
2
3
4
5
6
7
8
不能。多重 defer 嵌套。