云原生探索系列(十四):Go 语言panic、defer以及recover函数

前言

在 Go 语言中, panicrecoverdefer 是三个用于处理异常和程序控制流的关键字, 这篇文章探讨它们的用法及场景,达到在写 Go 程序时可以更加灵活地进行错误处理和异常控制的目的。

1. panic :运行时恐慌

无意引发 panic

看下面这段代码:

go 复制代码
func main() {
	s := []int{0, 1, 2, 3, 4}
	a := s[5]
	_ = a
}

这段代码,定义了一个切片,切片中有5个元素,索引为0~4,当我们访问索引为5的元素时,就会引发 panic

运行这段代码后,输出内容如下:

go 复制代码
panic: runtime error: index out of range [5] with length 5

goroutine 1 [running]:
main.main()
        /Users/GolandProjects/src/go-notes/main.go:5 +0x15

Process finished with the exit code 2

有意引发 panic

当程序调用 panic 函数时,当前函数的执行会停止,如果没有捕获该 panic ,程序将退出并输出相关的错误信息。

使用示例:

scss 复制代码
func causePanic01() {
	fmt.Println("执行函数causePanic01")
	causePanic02()
	fmt.Println("这行不会执行")
}

func causePanic02() {
	fmt.Println("执行函数causePanic02")
	panic("发生了不可恢复的错误")
	fmt.Println("这行不会执行")
}

func main() {
	fmt.Println("程序开始")
	causePanic01()
	fmt.Println("程序结束")
}

这段代码中,函数 causePanic02, 调用 panic("发生了不可恢复的错误") 后,程序立即停止执行当前函数并进入恐慌状态, panic 后的代码不会被执行,程序会退出并打印堆栈跟踪。

执行后,输出内容如下:

less 复制代码
程序开始
执行函数causePanic01
执行函数causePanic02
panic: 发生了不可恢复的错误

goroutine 1 [running]:
main.causePanic02()
        /Users/GolandProjects/src/go-notes/main.go:13 +0x5f
main.causePanic01()
        /Users/GolandProjects/src/go-notes/main.go:7 +0x55
main.main()
        /Users/GolandProjects/src/go-notes/main.go:19 +0x55

Process finished with the exit code 2

panic 被引发到程序停止发生过程

看上面这段输出结果, main 函数调用了 causePanic01 函数,而 causePanic01 函数调用了 causePanic02 函数, causePanic02 函数中代码执行信息先出现,然后是 causePanic01 函数中代码的执行信息,最后才是 main 函数的信息。

总结一下:当引发 panic 时,初始的 panic 详情会被建立起来,程序控制权会立即从此行代码转移至调用其所属函数的那行 代码时,也就是调用栈中的上一级,一级一级沿着调用栈反方向找,直到我们编写的最外层函数那里,最后,程序崩溃停止运行, 终止前panic 详情被打印出来

2. defer :延迟执行

defer 语句用于将一个函数调用推迟到当前函数的最后执行,无论当前函数是否正常返回, defer 后的函数都会执行。 它通常用于清理资源,如关闭文件、解锁互斥锁等。

使用案例

go 复制代码
func deferExample() {
    fmt.Println("开始执行函数")

    // defer 语句的调用顺序是逆序执行的
    defer fmt.Println("执行第一个 defer")
    defer fmt.Println("执行第二个 defer")

    fmt.Println("函数执行完毕")
}

func main() {
    deferExample()
}

这段代码执行后,输出结果如下:

go 复制代码
开始执行函数
函数执行完毕
执行第二个 defer
执行第一个 defer

为啥会这样输出呢?就是因为:

  • defer 语句的执行顺序是先进后出,即最后注册的 defer 会最先执行。
  • 在函数 deferExample 中, defer 语句在函数结束时按照逆序执行。

3. recover :恢复恐慌

recover 用于从恐慌中恢复,使程序继续执行。 recover 只能在 defer 函数中使用,并且只有当调用的 panic 发生时, recover 才会生效。如果 panic 没有发生, recover 返回 nil

为啥recover 只能在 defer 函数中使用?不可以单独使用吗?

下来看看下面这个案例:

go 复制代码
func main() {
	fmt.Println("程序开始")
	panic("发生错误")
	r := recover()
	fmt.Printf("panic: %s\n", r)
	fmt.Println("执行完成")
}

这段代码中,调用 panic 函数引发 panic ,然后调用 recover 函数恢复这个 panic ,运行后,程序依然崩溃, recover 函数 没有起到作用。原因通过上面的学习都知道, panic 一旦发生, panic 函数调用之后的代码都没有执行的机会。

正确使用 recover + defer 恢复恐慌

使用示例:

go 复制代码
func safeCall() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("恢复了 panic:", r)  // 捕获并恢复 panic
        }
    }()

    fmt.Println("开始执行...")
    panic("触发 panic")  // 触发 panic
    fmt.Println("这行不会执行")
}

func main() {
    safeCall()
    fmt.Println("程序继续执行")
}

执行这段代码,输出结果如下:

go 复制代码
开始执行...
恢复了 panic: 触发 panic
程序继续执行

这段代码中,在 safeCall 函数中, defer 捕获到 panic 并调用 recover ,仅当调用结果不为 nil 时,才会打印。 这样,即使发生了 panic ,程序也能恢复并继续执行后续代码。

4. 一个函数怎样才能把 panic 转化为 error 类型值,并将其作为函数的结果值返回给调用方

这个问题我们就可以使用这三个关键字来实现:

go 复制代码
func exampleFunction() (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("caught panic: %v", r)
		}
	}()

	panic("something went wrong")

	return nil // 这行不会执行,因为 panic 已经发生
}

func main() {
	err := exampleFunction()
	if err != nil {
		fmt.Println("Function returned an error:", err)
	} else {
		fmt.Println("Function executed successfully")
	}
}

这段代码,在 defer 中,使用 recover() 来捕获 panic 。如果 panic 被触发, recover() 会返回 panic 的参数值。 如果没有 panicrecover() 会返回 nil ,这时函数会正常执行并返回 nil

recover() 捕获到的 panic 通常是一个 interface{} 类型,可以是任何类型。你可以将它转化为一个 error 类型, 在这里,我们使用 fmt.Errorf("caught panic: %v", r) 来将 panic 的内容转化为 error 类型。

最后

这些三个关键字通常一起使用,在处理程序异常、错误恢复和资源清理时非常有用。

相关推荐
ivwdcwso3 小时前
Django-Vue3-Admin - 现代化的前后端分离权限管理系统
后端·python·django
AutoMQ3 小时前
知乎启用AutoMQ替换Kafka,开辟成本优化与运维提效新纪元
大数据·阿里云·云原生·kafka·云计算·腾讯云·aws·消息·gcp·计算·automq
黑色叉腰丶大魔王3 小时前
《Serverless 架构:引领未来软件开发的新趋势》
云原生·架构·serverless
怡雪~3 小时前
k8s的User Account
云原生·容器·kubernetes
码农小旋风3 小时前
00-MySQL专栏大纲
后端
MYG_G3 小时前
安装Go语言(Golang)的详细教程
开发语言·后端·golang
knoci3 小时前
【Go】-调度器简介
开发语言·后端·学习·golang
AskHarries4 小时前
如何在 Spring Cloud 集成 Micrometer Tracing?
后端
Hamm4 小时前
为了第三方快速对接,我们设计了这套基于Java的SDK方案
java·后端·架构