什么是 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{})
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")
}
![1594482-b81f0b614450707a](https://i-blog.csdnimg.cn/blog_migrate/7ad3ab9d936d9a91ede7d4ab6acc39af.webp?x-image-process=image/format,png)
如果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")
}
![1594482-a6541e9c256e6cb6](https://i-blog.csdnimg.cn/blog_migrate/41bcf9ec89b37f5e4f33f76ce212b6dd.webp?x-image-process=image/format,png)
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 程序会回到主函数继续执行
![1594482-9a26415d6ddfd449](https://i-blog.csdnimg.cn/blog_migrate/a5ad1e2cc31292f7570cd0798abb90fd.webp?x-image-process=image/format,png)
但是你发现没有错误日志输出了,如果我们希望将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")
}