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{})
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")
}
image.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")
}
image.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 程序会回到主函数继续执行

image.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")
}

你可能感兴趣的:(panic 和 recover)