在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 main

import "fmt"

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

输出:

panic: 异常

2、操作初始化map,slice等

package main
func 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 main

import "fmt"

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

多个panic情况

示例1

package main

import "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 main

import "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 main

import "fmt"

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

输出:

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

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

package main

import "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 main

import "fmt"

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

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

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

输出:

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

示例7

package main

import "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退出程序。

links

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

  • 目录
  • 上一节:
  • 下一节:

guyan0319
1.5k 声望721 粉丝

坚持但不盲目