Go Recover和Panic 组合使用
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() 来停止紧急情况序列。
上面程序输出如下
当程序发生 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()
来打印堆栈信息。
上述程序执行结果如下
从上面的输出我们可以看到,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() 函数中。 因此是无法恢复的。
上述程序输出如下:
我们在输出中看到,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
相关文章
在 JavaScript 中验证 Google ReCaptcha 第 2 版
发布时间:2024/03/23 浏览次数:193 分类:JavaScript
-
本文演示了如何在 JavaScript 中验证 Google Recaptcha。
C# 中的 goto 语句
发布时间:2024/02/02 浏览次数:184 分类:编程语言
-
本教程演示了如何在 C# 中使用 goto 以及何时使用它会有所帮助本教程将演示如何在 C# 中使用 goto 语法,并提供一些代码中的实际使用示例。
在 Python 中是否存在 goto 语句
发布时间:2023/12/20 浏览次数:197 分类:Python
-
本文为你提供了 Python 中是否存在 goto 语句的答案。本文为你提供了 Python 中是否存在 goto 语句的答案。基本上,Python 不支持 goto 语句。
避免 Python中的 TypeError: Input Expected at Most 1 Argument, Got 3 错误
发布时间:2023/07/08 浏览次数:671 分类:Python
-
Python 中避免 TypeError: input Expected atmost 1 argument, got 3 Error在Python编程中,我们有两个内置方法来获取用户的输入:input(prompt)和 raw_input(prompt)。
使用 Python 将文件上传到 Google 云端硬盘
发布时间:2023/06/15 浏览次数:544 分类:Python
-
本文将介绍我们如何使用 Python 将文件上传到云盘,以 Google Drive 为例。 为此,我们将使用 Google Drive API。
Python 错误 Valueerror: Expected 2d Array, Got 1d Array Instead
发布时间:2023/05/30 浏览次数:293 分类:Python
-
当我们在 numpy 中传递一维数组而不是二维数组时,会发生错误 ValueError: Expected 2D array, got 1D array instead 。如您所知,每种编程语言都会遇到很多错误,有些是在运行时,有些是在编译时。 Pyth