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{})
package main
 
import (
    "fmt"
)
 
func fullName(firstName *string, lastName *string) {
    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() {
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

defer 遇到 panic

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

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

package main
 
import (
    "fmt"
)
 
func fullName(firstName *string, lastName *string) {
    defer fmt.Println("deferred call in fullName")
 
    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")
}

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

recover
func recover() interface{}

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

package main
 
import (
    "fmt"
)
 
func fullName(firstName *string, lastName *string) {
    defer recover()
 
    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")
}

panic,recover 和 Go 协程

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

package main
 
import (
    "fmt"
)
 
func fullName(firstName *string, lastName *string) {
    defer recover() // 这样的写法不能恢复panic 
    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")
}

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

package main
 
import (
    "fmt"
)
 
func fullName(firstName *string, lastName *string) {
    defer r()
    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 r(){
    recover()
}
 
func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

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

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

package main
import (
    "fmt"
    "runtime/debug"
)
func fullName(firstName *string, lastName *string) {
    defer r()
 
    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 r(){
    if s := recover();s!=nil{
        fmt.Println(s)
        // 打印堆栈跟踪
        debug.PrintStack()
    }
}
 
func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0X码上链

你的鼓将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值