目录
一、简介
Go 语言追求简洁优雅,不支持传统的 try - catch - finally 这种方式捕获和处理异常,Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得更加混乱。
在 Go 语言中,使用多值返回来返回错误,不要用异常代替错误,更不要用来控制流程。
在遇到真正的异常的情况下(比如除数为0了),才使用 Exception处理。
Go 使用 panic / recover 模式来处理错误。panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效。
二、数据结构1、defer
defer 语句将一个函数放入一个列表(用栈表示其实更准确)中,该列表的函数在环绕 defer 的函数返回时会被执行。
// defer 数据结构
type _defer struct {
siz int32 // 参数和结果的内存大小
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 调用时参数的指针
arg interface{} // 调用 panic 时传入的参数
link *_panic // 指向了更早调用的 panic 结构
recovered bool // 表示当前 panic 是否被 recover 恢复
aborted bool // 表示当前的 panic 是否被强行终止
pc uintptr
sp unsafe.Pointer
goexit 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 main
import (
"fmt"
"runtime"
)
// 测试 recover
func 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 call
panic("抛出一个异常了,defer 会通过 recover 捕获这个异常,处理后续程序正常运行。")
fmt.Println("这里不会执行了")
}
func main() {
// 使用内置函数打印
println("Hello", "菜鸟实战")
// 测试 recover
test_recover()
// 测试 panic
test_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
菜鸟实战,持续学习!