迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 >

Go Recover和Panic 组合使用

作者:迹忆客 最近更新:2021/11/12 浏览次数:

recover 是一个内置函数,用于重新获得对 panic 程序的控制。

recover 函数原型如下

func recover() interface{}  

仅当在延迟函数内部调用时,recover 才有用。 在延迟函数内执行恢复调用通过恢复正常执行来停止 panic 序列,并检索传递给 panic 函数调用的错误消息。 如果在延迟函数之外调用 recover,它不会停止panic 序列。

让我们修改一下我们的程序,使用 recover 来恢复 panic 后的正常执行。

package main

import (  
    "fmt"
)

func recoverFullName() {  
    if r := recover(); r!= nil {
        fmt.Println("recovered from ", r)
    }
}

func fullName(firstName *string, lastName *string) {  
    defer recoverFullName()
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

运行示例

recoveryFullName() 函数调用recover() 返回传递给panic 函数调用的值。 这里我们只是打印了 recovery 返回的值。recoverFullName() 被推迟到fullName 函数。

当 fullName 发生紧急情况时,将调用延迟函数 recoveryName(),该函数使用 recovery() 来停止紧急情况序列。

上面程序输出如下

go 恢复panic程序

当程序发生 panic 时。延迟的 recoverFullName 函数被调用,该函数又调用 recover() 以重新获得对 panic 序列的控制。 对 recovery() 的调用返回传递给 panic() 的参数,因此它打印如下内容

recovered from  runtime error: last name cannot be nil 

执行 recover() 后,panic 停止,控制权返回给调用者,在这种情况下,是 main函数。 程序从 main 函数的 fmt.Println("returned normally from main") 开始继续正常执行,因为 panic 已经恢复了😃。 它打印如下文本

returned normally from main

然后打印

deferred call in main

让我们再看一个例子,我们从访问切片的无效索引引起的panic中恢复正常。

package main

import (  
    "fmt"
)

func recoverInvalidAccess() {  
    if r := recover(); r != nil {
        fmt.Println("Recovered", r)
    }
}

func invalidSliceAccess() {  
    defer recoverInvalidAccess()
    n := []int{5, 7, 4}
    fmt.Println(n[4])
    fmt.Println("normally returned from a")
}

func main() {  
    invalidSliceAccess()
    fmt.Println("normally returned from main")
}

运行示例

上述程序输出如下

Recovered runtime error: index out of range [4] with length 3  
normally returned from main  

从输出中,可以了解到我们已经从 panic 中恢复过来。

在 Recover 恢复之后获取堆栈信息

如果我们从 Panic 中恢复过来,我们就会丢失关于panic的堆栈跟踪。 在上述程序中,即使已经恢复了其实是丢失了堆栈跟踪的。

有一种方法,可以使用 Debug 包的 PrintStack 函数打印堆栈跟踪

package main

import (  
    "fmt"
    "runtime/debug"
)

func recoverFullName() {  
    if r := recover(); r != nil {
        fmt.Println("recovered from ", r)
        debug.PrintStack()
    }
}

func fullName(firstName *string, lastName *string) {  
    defer recoverFullName()
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

运行示例

在上面的程序中,我们使用 debug.PrintStack() 来打印堆栈信息。

上述程序执行结果如下

go 恢复panic打印堆栈信息

从上面的输出我们可以看到,panic被恢复了,并且打印如下信息

recovered from runtime error: last name cannot be nil

紧接着打印堆栈信息,然后打印如下结果

returned normally from main  
deferred call in main 

Panic、Recover 和 Goroutines

Recover 仅在从 panic 的同一个 goroutine 中调用时才起作用。 无法从不同 goroutine 中发生的 panic 中恢复过来。 让我们通过一个例子来理解这一点。

package main

import (  
    "fmt"
)

func recovery() {  
    if r := recover(); r != nil {
        fmt.Println("recovered:", r)
    }
}

func sum(a int, b int) {  
    defer recovery()
    fmt.Printf("%d + %d = %d\n", a, b, a+b)
    done := make(chan bool)
    go divide(a, b, done)
    <-done
}

func divide(a int, b int, done chan bool) {  
    fmt.Printf("%d / %d = %d", a, b, a/b)
    done <- true

}

func main() {  
    sum(5, 0)
    fmt.Println("normally returned from main")
}

运行示例

在上面的程序中,函数divide() 将会发生panic。 因为变量 b 是 nil 并且不可能将一个数除以零。 sum() 函数调用了一个延迟函数 recovery(),该函数用于从 panic 中恢复。 函数divide() 作为一个单独的goroutine 被调用。 我们在变量done的通道上进行等待。 确保divide() 完成执行。

你认为程序的输出是什么。 panic会恢复吗? 答案当然是不。 panic将无法恢复。 这是因为 recover 函数存在于不同的 goroutine 中,而panic发生在不同 goroutine 中的divide() 函数中。 因此是无法恢复的。

上述程序输出如下:

go 不同goroutine中的recover和panic

我们在输出中看到,recover并没有执行。

如果divide() 函数在同一个goroutine 中被调用,我们就会从 panic 中恢复过来。

如果将上面程序中的

go divide(a, b, done)

改为

divide(a, b, done)

recover将会执行,因为它和panic在同一个 goroutine 中。 如果程序按照上面进行更改,则打印结果如下

5 + 0 = 5  
recovered: runtime error: integer divide by zero  
normally returned from main  

到此我们就将 recover 介绍完成了。

本篇除了Recover之外,还涉及到Panic。推荐阅读Go 语言中错误处理的 Panic

除非注明转载,本站文章均为原创或翻译,欢迎转载,转载请以链接形式注明出处

本文地址:

迹忆客

专注技术分享,项目实战分享!

技术宅 乐于分享 7年编程经验
社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

热门文章

教程更新

热门标签

Go