• golang中的panic 和 recover


    什么是 panic?

    在 Go 语言中,程序中一般是使用错误来处理异常情况。对于程序中出现的大部分异常情况,错误就已经够用了。

    但在有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 panic 来终止程序。当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪(Stack Trace),最后程序终止。在编写一个示例程序后,我们就能很好地理解这个概念了。

    在本教程里,我们还会接着讨论,当程序发生 panic 时,使用 recover 可以重新获得对该程序的控制。

    可以认为 panic 和 recover 与其他语言中的 try-catch-finally 语句类似,只不过一般我们很少使用 panic 和 recover。而当我们使用了 panic 和 recover 时,也会比 try-catch-finally 更加优雅,代码更加整洁。

    什么时候应该使用 panic?
    需要注意的是,你应该尽可能地使用错误,而不是使用 panic 和 recover。只有当程序不能继续运行的时候,才应该使用 panic 和 recover 机制。

    panic 有两个合理的用例。

    发生了一个不能恢复的错误,此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic,因为如果不能绑定端口,啥也做不了。

    发生了一个编程上的错误。 假如我们有一个接收指针参数的方法,而其他人使用 nil 作为参数调用了它。在这种情况下,我们可以使用 panic,因为这是一个编程错误:用 nil 参数调用了一个只能接收合法指针的方法。

    这是一个内建函数,传入数据即可

    func panic(interface{})
    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func fullName(firstName *string, lastName *string) {
    6. if firstName == nil {
    7. panic("runtime error: first name cannot be nil")
    8. }
    9. if lastName == nil {
    10. panic("runtime error: last name cannot be nil")
    11. }
    12. fmt.Printf("%s %s\n", *firstName, *lastName)
    13. fmt.Println("returned normally from fullName")
    14. }
    15. func main() {
    16. firstName := "Elon"
    17. fullName(&firstName, nil)
    18. fmt.Println("returned normally from main")
    19. }

    defer 遇到 panic

    我们重新总结一下 panic 做了什么。当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪,最后程序终止

    panic 其实是一个终止函数栈执行的过程,但是在函数退出前都会执行defer里面的函数,知道所有的函数都退出后,才会执行panic

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func fullName(firstName *string, lastName *string) {
    6. defer fmt.Println("deferred call in fullName")
    7. if firstName == nil {
    8. panic("runtime error: first name cannot be nil")
    9. }
    10. if lastName == nil {
    11. panic("runtime error: last name cannot be nil")
    12. }
    13. fmt.Printf("%s %s\n", *firstName, *lastName)
    14. fmt.Println("returned normally from fullName")
    15. }
    16. func main() {
    17. defer fmt.Println("deferred call in main")
    18. firstName := "Elon"
    19. fullName(&firstName, nil)
    20. fmt.Println("returned normally from main")
    21. }

    如果defer中也有panic 那么会依次按照发生panic的顺序执行

    recover
    func recover() interface{}

    主要在defer 中才有效,这个一定要记住

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func fullName(firstName *string, lastName *string) {
    6. defer recover()
    7. if firstName == nil {
    8. panic("runtime error: first name cannot be nil")
    9. }
    10. if lastName == nil {
    11. //panic("runtime error: last name cannot be nil")
    12. }
    13. //fmt.Printf("%s %s\n", *firstName, *lastName)
    14. fmt.Println("returned normally from fullName")
    15. }
    16. func main() {
    17. defer fmt.Println("deferred call in main")
    18. firstName := "Elon"
    19. fullName(&firstName, nil)
    20. fmt.Println("returned normally from main")
    21. }

    panic,recover 和 Go 协程

    recover 只能回复同一个协程中的panic

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func fullName(firstName *string, lastName *string) {
    6. defer recover() // 这样的写法不能恢复panic
    7. if firstName == nil {
    8. panic("runtime error: first name cannot be nil")
    9. }
    10. if lastName == nil {
    11. panic("runtime error: last name cannot be nil")
    12. }
    13. fmt.Printf("%s %s\n", *firstName, *lastName)
    14. fmt.Println("returned normally from fullName")
    15. }
    16. func main() {
    17. defer fmt.Println("deferred call in main")
    18. firstName := "Elon"
    19. fullName(&firstName, nil)
    20. fmt.Println("returned normally from main")
    21. }

    依然报错
    请使用下面的方式恢复

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func fullName(firstName *string, lastName *string) {
    6. defer r()
    7. if firstName == nil {
    8. panic("runtime error: first name cannot be nil")
    9. }
    10. if lastName == nil {
    11. panic("runtime error: last name cannot be nil")
    12. }
    13. fmt.Printf("%s %s\n", *firstName, *lastName)
    14. fmt.Println("returned normally from fullName")
    15. }
    16. func r(){
    17. recover()
    18. }
    19. func main() {
    20. defer fmt.Println("deferred call in main")
    21. firstName := "Elon"
    22. fullName(&firstName, nil)
    23. fmt.Println("returned normally from main")
    24. }

    只要程序发生panic,就会到调用延时函数 defer r() r函数会恢复这个panic 程序会回到主函数继续执行

    但是你发现没有错误日志输出了,如果我们希望将panic的错误栈数据显示出来怎么办呢?

    1. package main
    2. import (
    3. "fmt"
    4. "runtime/debug"
    5. )
    6. func fullName(firstName *string, lastName *string) {
    7. defer r()
    8. if firstName == nil {
    9. panic("runtime error: first name cannot be nil")
    10. }
    11. if lastName == nil {
    12. panic("runtime error: last name cannot be nil")
    13. }
    14. fmt.Printf("%s %s\n", *firstName, *lastName)
    15. fmt.Println("returned normally from fullName")
    16. }
    17. func r(){
    18. if s := recover();s!=nil{
    19. fmt.Println(s)
    20. // 打印堆栈跟踪
    21. debug.PrintStack()
    22. }
    23. }
    24. func main() {
    25. defer fmt.Println("deferred call in main")
    26. firstName := "Elon"
    27. fullName(&firstName, nil)
    28. fmt.Println("returned normally from main")
    29. }

  • 相关阅读:
    26-k8s的附加组件-图形化管理工具dashboard
    Promise, async, await实现异步编程,代码详解
    标准IO移动光标
    技术分享| 应急指挥调度平台需要这些技术支撑
    外贸公司保密协议
    [GHCTF 2024 新生赛]ezzz_unserialize
    java中数据类型byte的底层原理透析
    WS、WebService、HTTPDNS、RESTful、FTP、邮件
    流程建模艺术:使用Activiti设计流程
    水一下文章
  • 原文地址:https://blog.csdn.net/waysoflife/article/details/133811725