> 文档中心 > 【Go实战基础】Go 是如何捕获及处理异常的

【Go实战基础】Go 是如何捕获及处理异常的

目录

一、简介

二、数据结构

1、defer

2、panic

3、recover

三、菜鸟实战

1、创建 g007.go

2、编译和运行

3、运行结果


一、简介

Go 语言追求简洁优雅,不支持传统的 try - catch - finally 这种方式捕获和处理异常,Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得更加混乱。

在 Go 语言中,使用多值返回来返回错误,不要用异常代替错误,更不要用来控制流程。
在遇到真正的异常的情况下(比如除数为0了),才使用 Exception处理。

Go 使用 panic / recover 模式来处理错误。panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效。

二、数据结构

1、defer

defer 语句将一个函数放入一个列表(用栈表示其实更准确)中,该列表的函数在环绕 defer 的函数返回时会被执行。

// defer 数据结构type _defer struct {   sizint32      // 参数和结果的内存大小started   bool// defer 是否执行    heap      bool// 是否在堆上openDefer bool// 当前 defer 是否经过开放编码的优化sp uintptr    // 栈指针pc uintptr    // 调用方的程序计数器fn *funcval   // defer 关键字中传入的函数_panic    *_panic    // 触发延迟调用的结构体,可能为空link      *_defer    // 指向 defer 链表}

defer 通常用于简化函数的各种各样清理动作,例如关闭文件,解锁等等的释放资源的动作。

2、panic

panic 是内建的停止控制流的函数,相当于其他编程语言的抛异常操作 throw exception 。当函数 F 调用了 panic,F 的执行会被停止,在 F 中 panic 前面定义的 defer 操作都会被执行,然后 F 函数返回。
对于调用者来说,调用 F 的行为就像调用 panic(如果F函数内部没有把 panic recover 掉),如果都没有捕获该 panic,相当于一层层 panic,程序将会 crash。panic 可以直接调用,也可以是程序运行时错误导致,例如数组越界等。

// panic 数据结构type _panic struct {argp      unsafe.Pointer    // 指向 defer 调用时参数的指针arginterface{}// 调用 panic 时传入的参数link      *_panic    // 指向了更早调用的 panic 结构recovered bool// 表示当前 panic 是否被 recover 恢复aborted   bool// 表示当前的 panic 是否被强行终止pc uintptrsp unsafe.Pointergoexit    bool}

结构体中的 pc、sp 和 goexit 三个字段都是为了修复 runtime.Goexit 的问题引入的。该函数能够只结束调用该函数的 Goroutine 而不影响其他的 Goroutine,但是该函数会被 defer 中的 panic 和 recover 取消,引入这三个字段的目的就是为了解决这个问题。

3、recover

recover 是一个从 panic 恢复的内建函数,recover 只有在 defer 的函数里面才能发挥真正的作用。如果是正常的情况(没有发生 panic ),调用 recover 将会返回 nil 并且没有任何影响。如果当前的 goroutine panic了,recover 的调用将会捕获到 panic 的值,并且恢复正常执行。
 

三、菜鸟实战

实战需求: Go 是如何捕获及处理异常的

马上安排!

1、创建 g007.go

/* * @Author: 菜鸟实战 * @FilePath: /go110/go-007/g007.go * @Description: 崩溃和异常,defer, panic, recover */package mainimport ("fmt""runtime")// 测试 recoverfunc test_recover() {// 采用 defer + recover 来捕获和处理异常// 匿名函数形式调用:func(){}()defer func() {err := recover() //recover 内置函数捕获异常if err != nil {  // nil 是 err 的零值fmt.Println("err = ", err)//runtime error: index out of range [3] with length 3}}()arr := []string{"a", "b", "c"}str := arr[3]fmt.Println("str = ", str)}/** panic 一般会导致程序挂掉(除非 recover ),  然后 Go 运行时会打印出调用栈但是,关键的一点是,即使函数执行的时候 panic 了,函数不往下走了,运行时并不是立刻向上传递 panic,而是到defer那,等 defer 的东西都跑完了,panic再向上传递。所以 defer 有点类似 try-catch-finally 中的 finally。**/func test_panic() {// 先声明 defer, 捕获 panic 异常// 匿名函数的调用方式:func(){}()defer func() {if err := recover(); err != nil {fmt.Println("捕获到了 panic 产生的异常: ", err)fmt.Println("捕获到 panic 的异常,recover 恢复回来。")}}()// 不写会报 expression in defer must be function callpanic("抛出一个异常了,defer 会通过 recover 捕获这个异常,处理后续程序正常运行。")fmt.Println("这里不会执行了")}func main() {// 使用内置函数打印println("Hello", "菜鸟实战")// 测试 recovertest_recover()// 测试 panictest_panic()// 当前版本fmt.Printf("版本: %s \n", runtime.Version())}

2、编译和运行

# 1、生成模块依赖
go mod init g007
 
# 2、编译
go build g007.go 
 
# 3、编译后的目录结构
 
└── go-007
    ├── g007
    ├── g007.go
    └── go.mod
 
# 4、运行
go run g007

3、运行结果

Hello 菜鸟实战
err =  runtime error: index out of range [3] with length 3
捕获到了 panic 产生的异常:  抛出一个异常了,defer会通过recover捕获这个异常,处理后续程序正常运行。
捕获到 panic 的异常,recover 恢复回来。
版本: go1.17.10 

菜鸟实战,持续学习!