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
相关文章
使用 C 语言中的 goto 语句
发布时间:2023/05/07 浏览次数:79 分类:C语言
-
本文介绍了如何在 C 语言中使用 goto 语句。使用 goto 语句在 C 语言中实现循环 goto 关键字是 C 语言的一部分,它提供了一个做无条件跳转的结构。
Django 中的 Slug
发布时间:2023/05/04 浏览次数:173 分类:Python
-
本篇文章旨在定义一个 slug 以及我们如何使用 slug 字段在 Python 中使用 Django 获得独特的帖子。
在 Django 中按降序过滤查询集中的项目
发布时间:2023/05/04 浏览次数:157 分类:Python
-
在这个讲解中,学习如何借助 Django 中的 order_by() 方法按降序过滤出查询集中的项目。
Django ALLOWED_HOSTS 介绍
发布时间:2023/05/04 浏览次数:181 分类:Python
-
本文展示了如何创建您的 Django 网站,为公开发布做好准备,如何设置 ALLOWED_HOSTS 以及如何在使用 Django 进行 Web 部署期间修复预期的主要问题。
Django 中的 Select_related 方法
发布时间:2023/05/04 浏览次数:129 分类:Python
-
本文介绍了什么是查询集,如何处理这些查询以及我们如何利用 select_related() 方法来过滤 Django 中相关模型的查询。
使用 Post 请求将数据发送到 Django 服务器
发布时间:2023/05/04 浏览次数:159 分类:Python
-
在这篇关于Django的讲解中,我们简要介绍了post和get请求以及如何在Django中用post实现CSRF token。
Django 返回 JSON
发布时间:2023/05/04 浏览次数:106 分类:Python
-
在与我们的讨论中,我们简要介绍了 JSON 格式,并讨论了如何借助 Django 中的 JsonResponse 类将数据返回为 JSON 格式。
在 Django 中创建对象
发布时间:2023/05/04 浏览次数:59 分类:Python
-
本文的目的是解释什么是模型以及如何使用 create() 方法创建对象,并了解如何在 Django 中使用 save() 方法。