讲解panice与recover

1. 前言

Go语言追求简洁优雅,Go语言不支持传统的 try…catch…finally 这种异常。所以我们说一下go中的try…catch{}

2. 什么是panic、recover

  • panic 主要用于主动抛出异常,类似 java 等语言中的 throw
    panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 goroutine 中递归执行调用方的 defer

  • recover 主要用于捕获异常,让程序回到正常状态,类似 java 等语言中的 try … catch
    recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用

2.1 recover必须要要在defer函数中使用,否则无法阻止panic
func example1()  {
    defer func() {
        if err := recover(); err !=nil{
            fmt.Println(err)
        }
    }()
    panic("example1 panic")
}

func example2()  {
    defer recover()
    panic("example2 panic")
}
func main()  {
    example1()
    example2()
}

运行发现example2()方法的panic是没有被recover到,导致整个程序直接崩了。因为defer将语句放入到栈中时,也会将相关的值拷贝同时入栈。所以defer recover()这种写法在放入defer栈中时就已经被执行过了,panic是发生在之后,所以根本无法阻止住panic

3. 特性

  • recover 只有在 defer 函数中使用才有效
  • panic 允许在 defer 中嵌套多次调用.程序多次调用 panic 也不会影响 defer 函数的正常执行,所以使用 defer 进行收尾工作一般来说都是安全的。
    func example3()  {
        defer fmt.Println("this is a example3 for defer use panic")
        defer func() {
            defer func() {
                panic("panic defer 2")
            }()
            panic("panic defer 1")
        }()
        panic("panic example3")
    }
    
  • panic 只会对当前 Goroutinedefer 有效,因此当程序发生 panic 时只会调用当前 Goroutine 的延迟调用函数是没有问题的
    func main()  {
        go example4()
        go example5()
        time.Sleep(10 * time.Second)
    }
    
    func example4()  {
        fmt.Println("goroutine example4")
        defer func() {
            fmt.Println("test defer")
        }()
        panic("unknown")
    }
    
    func example5()  {
        defer fmt.Println("goroutine example5")
        time.Sleep(5 * time.Second)
    }
    
    这里我开了两个协程,一个协程会发生 panic,导致程序崩溃,但是只会执行自己所在 Goroutine 的延迟函数,所以正好验证了多个 Goroutine 之间没有太多的关联,一个 Goroutinepanic 时也不应该执行其他 Goroutine 的延迟函数。

4. 应用

在实际项目开发中,经常会遇到 panic 问题, Go 的 runtime 代码中很多地方都调用了 panic 函数,对于不了解 Go 底层实现的新人来说,这无疑是挖了一堆深坑。我们在实际生产环境中总会出现 panic,但是我们的程序仍能正常运行,这是因为我们的框架已经做了recover,他已经为我们兜住底,比如 gin

4.1 踩过的坑

在使用 gin 时,第一步会初始化一个 Engine 实例,调用 Default 方法会把 recovery middleware 附上, recovery 中使用了 defer 函数,通过 recover 来阻止 panic ,当发生 panic 时,会返回500错误码。这里有一个需要注意的点是只有主程序中的 panic 是会被自动 recover 的,协程中出现 panic 会导致整个程序垮掉。

我们上面讲的第三个特性,一个协程会发生panic,导致程序崩溃,但是只会执行自己所在 Goroutine 的延迟函数,所以正好验证了多个 Goroutine 之间没有太多的关联,一个 Goroutinepanic 时也不应该执行其他 Goroutine 的延迟函数。

所以为了程序健壮性,我们应该自己主动检查我们的协程程序,在我们的协程函数中添加 recover 是很有必要的。

func main()  {
        r := gin.Default()
        r.GET("/test/panic", func(ctx *gin.Context) {
            go func() {
                defer func() {
                    if err := recover();err != nil{
                        fmt.Println(err)
                    }
                }()
                panic("panic")
            }()
        })
        r.Run()
}

如果使用的Gin框架,切记要检查协程中是否会出现panic,否则线上代价很大。

5. 经典的panic

  • 数组/切片下标越界,对于go这种静态语言来说,下标越界是致命问题。
  • 不要访问未初始化的指针或nil指针
  • 不要往已经closechan里发送数据
  • map不是线程安全的,不要并发读写map
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值