【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 {
   
	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 

菜鸟实战,持续学习!

你可能感兴趣的:(GO,实战基础,golang,开发语言,后端)