Go panic+defer+recover理解加使用

业务逻辑中,Golang通过返回error捕获错误,但当遇到一些触发程序的异常时,会导致程序崩溃,这时就是需要recover这种捕获异常方式了,recover通常与defer同时出现

Defer

defer语句函数放入栈中,执行defer的顺序满足先进后出原则,严格按照这个顺序,不会因为return等操作就不执行

举一个简单的recover和defer配合使用的例子

import (
    "fmt"
    "time"
)
func main() {
    test()
    for {
        fmt.Println("main()下面的代码...")
        time.Sleep(time.Second)
    }
}
func test() {
    defer func() {
        err := recover() // recover()内置函数,可以捕获到异常
        if err != nil {  // 捕获到异常
            fmt.Println("err=", err)
        }
    }()
    num1 := 10
    num2 := 0
    res := num1 / num2
    fmt.Println("res=", res)
}

打印:

err= runtime error: integer divide by zero
main()下面的代码...
main()下面的代码...

使用defer函数的一些坑

1.不要在循环中使用defer,defer函数会入栈,可能会因为爆栈而导致程序崩溃,解决办法(1.在循环之外定义defer,2.将循环体包成方法体,在方法体中调用defer)

2.调用defer时参数问题,执行到defer函数时,就会进行参数复制(记住这个复制的时刻,并不是等最后执行的时候才是复制),此时复制是值复制(等同于普通函数的入参)

3.defer语句延迟执行了一个匿名函数,因为这个匿名函数捕获了外部函数的局部变量v,这种函数我们一般叫闭包。闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问

func Inc() (v int) {
    defer func(){ v++ } ()
    return 42
}
print(Inc)#43

another case

func main() {
    for i := 0; i < 5; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}
记录的是引用,最后加完的i就是5
5 5 5 5 5

4.跨协程失效,panic只会触发当前协程的defer函数

func main() {
    defer println("in main")
    go func() {
        defer println("in goroutine")
        panic("")
    }()

    time.Sleep(1 * time.Second)
}

in goroutine

panic : 

1分钟后输出 in main

5.失效的崩溃恢复,recover不在defer里面,所以不会被捕获到

func main() {
    defer fmt.Println("in main")
    if err := recover(); err != nil {
        fmt.Println(err)
    }

    panic("unknown err")
}

6.嵌套崩溃

func main() {
    defer fmt.Println("in main")
    defer func() {
        defer func() {
            panic("panic again and again")
        }()
        panic("panic again")
    }()

    panic("panic once")
}
in main
panic: panic once
	panic: panic again
	panic: panic again and again
goroutine 1 [running]:

7.如果其他协程发生panic,不捕获的话会自己导致主线程和其他协程执行失败,并且在主线程无法捕获panic

原文地址:https://www.cnblogs.com/peterleee/p/13448587.html