探索Go语言中的panic机制

发表时间: 2023-11-28 00:32

在go语言中,panic是一种用于处理不可恢复错误和异常情况的机制。大多数情况下,我们用panic来快速解决正常运行中出现的异常情况,或者我们没有准备好优雅地处理的错误。

源码版本:go1.21.4

panic源码

runtime/runtime2.go

type _panic struct {    argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink 指针指向panic期间运行的延迟调用的参数;     arg       any            // argument to panic //参数    link      *_panic        // link to earlier panic 链接下一个 panic 结构体    pc        uintptr        // where to return to in runtime if this panic is bypassed 如果绕过此panic,则在运行时返回到何处    sp        unsafe.Pointer // where to return to in runtime if this panic is bypassed    recovered bool           // whether this panic is over   panic是否已经结束?    aborted   bool           // the panic was aborted panic停止了    goexit    bool}

触发panic条件

1、用panic函数主动触发

package mainimport "fmt"func main(){panic("异常")fmt.Println("end")}

输出:

panic: 异常

2、操作初始化map,slice等

package mainfunc main(){    //示例1    //var d map[string]string    //d["a"]="a"    //示例2    //var c []string    //c[1]="aa"    //示例3    var h chan int    close(h)}

捕获panic信息

通过defer和recover捕获

package mainimport "fmt"func main(){    defer func() {        if err := recover(); err != nil {            fmt.Println(err)        }    }()    panic("被捕获错误信息")    fmt.Println("hello")}

多个panic情况

示例1

package mainimport "fmt"func main(){    defer func() {        if err := recover(); err != nil {            fmt.Println(err)        }    }()    panic("被捕获错误信息1")    panic("被捕获错误信息2")    fmt.Println("hello")}

输出:被捕获错误信息1

因为执行第一个panic就结束程序,不会执行后面程序,所以不会只执行第二个panic
示例2

package mainimport "fmt"func main(){    defer func() {        if err := recover(); err != nil {            fmt.Println(err)        }    }()    defer panic("被捕获错误信息1")    panic("被捕获错误信息2")    fmt.Println("hello")}

输出:被捕获错误信息1

这个和上面一样也不会执行第二个panic
示例3

package mainimport "fmt"func main(){    defer panic("被捕获错误信息1")    defer func() {        if err := recover(); err != nil {            fmt.Println(err)        }    }()    panic("被捕获错误信息2")    fmt.Println("hello")}

输出:

被捕获错误信息2panic: 被捕获错误信息1

先进入defer的 panic,会被执行
示例4

package mainimport "fmt"func main(){    defer func() {        if err := recover(); err != nil {            fmt.Println(err)        }    }()    a:=test()    fmt.Println(a)    panic("被捕获错误信息1")    fmt.Println("hello")}func test() (a int)  {    a=0    panic("被捕获错误信息2")    return }

输出:被捕获错误信息2
示例5

package mainimport "fmt"func main(){    defer func() {        if err := recover(); err != nil {            fmt.Println(err)        }    }()    go func() {        panic("被捕获错误信息1")    }()}

没有捕捉到panic,说明不同的goroutine,不共享defer
示例6

package mainfunc main(){    defer panic("被捕获错误信息1")    defer panic("被捕获错误信息2")    defer panic("被捕获错误信息3")}

输出:

panic: 被捕获错误信息3    panic: 被捕获错误信息2    panic: 被捕获错误信息1

示例7

package mainimport "fmt"func main(){    defer func() {        if err := recover(); err != nil {            fmt.Println(err)        }    }()    defer panic("被捕获错误信息1")    defer panic("被捕获错误信息2")    defer panic("被捕获错误信息3")}

输出:被捕获错误信息1
recover只捕获1其他的没有捕获,退出了程序

总结

1、defer、 recover、panic 三者之间顺序 recover放入defer,捕捉panic结束程序之前输出的信息。
2、defer 和recover必须一起才能捕捉panic,如果单独recover,放在panic之前,没有相应错误信息,捕捉不到,放在panic之后,则panic发出结束程序后,不会执行recover。
3、多个panic放入defer会都输出。
4、不同的goroutine,不能捕捉对方的panic信息,不共享defer中的队列。
5、recover只捕捉之后紧邻的panic信息。
6、recover除了捕捉panic信息外,还接管panic退出程序。

参考

https://zhuanlan.zhihu.com/p/418257257
https://gfw.go101.org/article/panic-and-recover-more.html
https://www.geeksforgeeks.org/panic-in-golang/