迹忆客 专注技术分享

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

Go Recover和Panic 组合使用

作者:迹忆客 最近更新:2023/01/08 浏览次数:

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

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

使用 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 ALLOWED_HOSTS 介绍

发布时间:2023/05/04 浏览次数:181 分类:Python

本文展示了如何创建您的 Django 网站,为公开发布做好准备,如何设置 ALLOWED_HOSTS 以及如何在使用 Django 进行 Web 部署期间修复预期的主要问题。

Django 中的 Select_related 方法

发布时间:2023/05/04 浏览次数:129 分类:Python

本文介绍了什么是查询集,如何处理这些查询以及我们如何利用 select_related() 方法来过滤 Django 中相关模型的查询。

在 Django 中上传媒体文件

发布时间:2023/05/04 浏览次数:198 分类:Python

在本文中,我们简要介绍了媒体文件以及如何在 Django 项目中操作媒体文件。

Django 返回 JSON

发布时间:2023/05/04 浏览次数:106 分类:Python

在与我们的讨论中,我们简要介绍了 JSON 格式,并讨论了如何借助 Django 中的 JsonResponse 类将数据返回为 JSON 格式。

在 Django 中创建对象

发布时间:2023/05/04 浏览次数:59 分类:Python

本文的目的是解释什么是模型以及如何使用 create() 方法创建对象,并了解如何在 Django 中使用 save() 方法。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便