Golang语法系列——recover异常处理

本文最后更新于:1 年前

现象

  • panic 只会触发当前 Goroutine 的 defer
  • recover 只有在 defer 中调用才会生效;
  • panic 允许在 defer 中嵌套多次调用;

跨协程失效

panic 只会触发当前 Goroutine 的延迟函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
defer println("in main")
go func() {
defer println("in goroutine")
panic("")
}()

time.Sleep(1 * time.Second)
}

$ go run main.go
in goroutine
panic:
...

当我们运行这段代码时会发现 main 函数中的 defer 语句并没有执行,执行的只有当前 Goroutine 中的 defer

失效的崩溃恢复

在主程序中调用 recover 试图中止程序的崩溃,但是从运行的结果中我们也能看出,下面的程序没有正常退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
defer fmt.Println("in main")
if err := recover(); err != nil {
fmt.Println(err)
}

panic("unknown err")
}

$ go run main.go
in main
panic: unknown err

goroutine 1 [running]:
main.main()
...
exit status 2

recover 只有在发生 panic 之后调用才会生效。然而在上面的控制流中,recover 是在 panic 之前调用的,并不满足生效的条件,所以我们需要在 defer 中使用 recover 关键字

嵌套崩溃

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
31
32
33
34
func main() {
defer func(){
fmt.Println("5")
fmt.Println("in main")
}()
defer func() {
fmt.Println("2")
defer func() {
fmt.Println("4")
panic("panic again and again and again")
}()
defer func() {
fmt.Println("3")
panic("panic again and again")
}()
panic("panic again")
}()
fmt.Println("1")
panic("panic once")
}

>>>
1
2
4
5
6
in main

panic: panic once
panic: panic again
panic: panic again and again
panic: panic again and again and again
exit status 2

panic 先执行同级defer,然后同级defer按FILO顺序执行,若defer内部嵌套defer, 其执行顺序按内部嵌套FILO顺序执行。同时注意panic位置在defer 前面则同级后面defer不会执行

panic()和recover()和defer运用

  • 程序panic,defer中使用recover恢复
    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
    31
    32
    #执行panic,这时recover就不等于nil,然后程序先执行defer,然后panic终止
    #类似于try catch
    package main

    import (
    "log"
    )

    func protect(g func()) {
    defer func() {
    log.Println("done")
    // Println executes normally even if there is a panic
    if err := recover(); err != nil {
    log.Printf("run time panic: %v", err)
    }
    }()
    log.Println("start")
    g() // possible runtime-error
    }
    func main() {
    protect(func() {
    log.Println("in g()")
    panic("dropout g()")
    })
    }

    输出
    2018/07/25 18:21:23 start
    2018/07/25 18:21:23 in g()
    2018/07/25 18:21:23 done
    2018/07/25 18:21:23 run time panic: dropout g()