美文网首页
panic 和 recover

panic 和 recover

作者: 酷走天涯 | 来源:发表于2018-12-24 09:18 被阅读8次

    什么是 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

          本文链接:https://www.haomeiwen.com/subject/fvcyzftx.html