Go语言处理异常的三种方式(defer、panic和recover)
大部分编程语言都支持异常机制,如 Python 的 try except,异常机制不仅能处理程序中出现的异常,还能实现程序的流程控制。
Go 语言追求简洁优雅,所以不支持传统的异常机制,如果将异常与流程控制混在一起,很容易使代码变得混乱,并且开发者很容易滥用异常,为了一个小错误而抛出异常,这样不符合 Go 语言的设计要求。
Go 语言没有异常机制,但提供了宕机功能,它与其他编程语言的自定义异常是同一概念,本文将 Go 语言的宕机统一称为异常。
虽然不建议使用异常机制,但在极个别的情况下不得不使用异常处理错误,比如除数为 0 的时候执行运行等情况,因此引入了异常处理,异常处理的关键字分别为 defer、panic 和 recover。
关键字 panic 在 Go 语言中是以内置函数 panic() 表示的,内置函数 panic() 可使程序提示异常而终止运行,它设有参数 v,参数类型为空接口,参数 v 代表异常信息。
示例如下:
关键字 defer 不仅能作为函数的回调函数,它在开发中也十分常用,比如在 sync 加锁后执行锁释放,在 sync 同步等待组调用 Done() 方法等场景。
如果关键字 defer 与 panic 结合使用,defer 必须在 panic 前面,示例如下:
如果 defer 在 panic 后面,程序抛出异常的时候不会执行 defer 的代码,并且 GoLand 会对 defer 的代码标黄,如下图所示。
关键字 recover 以内置函数 recover() 表示,函数返回值为空接口,代表程序的异常信息,它必须与关键字 defer 结合使用才能实现异常捕捉和处理,示例如下:
综上所述,defer、recover() 和 panic() 的关系说明如下:
Go 语言追求简洁优雅,所以不支持传统的异常机制,如果将异常与流程控制混在一起,很容易使代码变得混乱,并且开发者很容易滥用异常,为了一个小错误而抛出异常,这样不符合 Go 语言的设计要求。
Go 语言没有异常机制,但提供了宕机功能,它与其他编程语言的自定义异常是同一概念,本文将 Go 语言的宕机统一称为异常。
虽然不建议使用异常机制,但在极个别的情况下不得不使用异常处理错误,比如除数为 0 的时候执行运行等情况,因此引入了异常处理,异常处理的关键字分别为 defer、panic 和 recover。
Go panic自主抛出异常
关键字 panic 是让开发者能自主抛出异常,使程序进入宕机状态而终止运行,示例代码如下:package main func main() { panic("这是自定义异常") }运行上述代码,运行结果为:

关键字 panic 在 Go 语言中是以内置函数 panic() 表示的,内置函数 panic() 可使程序提示异常而终止运行,它设有参数 v,参数类型为空接口,参数 v 代表异常信息。
Go defer延时执行
关键字 defer 具有延时执行的作用,在一个函数中,只要某行代码使用了关键字 defer,它都会被最后执行,如果函数设有返回值,则在代码执行完成后和函数返回值之间执行。示例如下:
package main import "fmt" func myFunc() int { defer fmt.Printf("这是defer\n") fmt.Printf("这是函数的业务逻辑\n") return 1 } func main() { fmt.Printf("这是函数返回值:%v\n", myFunc()) }运行上述代码,运行结果为:
这是函数的业务逻辑
这是defer
这是函数返回值:1
关键字 defer 不仅能作为函数的回调函数,它在开发中也十分常用,比如在 sync 加锁后执行锁释放,在 sync 同步等待组调用 Done() 方法等场景。
如果关键字 defer 与 panic 结合使用,defer 必须在 panic 前面,示例如下:
package main import "fmt" func main() { defer fmt.Printf("这是defer\n") panic("这是自定义异常") }当 defer 在 panic 前面的时候,程序在抛出异常之前就会执行 defer 的代码,代码运行结果如下图所示:

如果 defer 在 panic 后面,程序抛出异常的时候不会执行 defer 的代码,并且 GoLand 会对 defer 的代码标黄,如下图所示。

Go recover宕机时恢复执行
使用 panic() 抛出异常,程序就会停止执行,如果让程序在异常情况下仍能继续运行,可以结合 defer 和 recover 实现异常捕捉和恢复,它与其他编程语言的异常捕捉和处理(try/catch 机制)是同一概念。关键字 recover 以内置函数 recover() 表示,函数返回值为空接口,代表程序的异常信息,它必须与关键字 defer 结合使用才能实现异常捕捉和处理,示例如下:
package main import "fmt" func myFunc() { // 定义延时执行的匿名函数 defer func() { // 使用recover()捕捉异常 if err := recover(); err != nil { // err不为空值,说明主动抛出异常 fmt.Printf("捕捉异常:%v\n", err) } else { // err为空值,说明程序没有抛出异常 fmt.Println("程序没有异常") } }() // 正常执行程序 fmt.Println("程序正常运行") // 主动抛出异常 panic("这是自定义异常") } func main() { // 调用函数 myFunc() }运行上述代码,运行结果为:
程序正常运行
捕捉异常:这是自定义异常
- 在函数 myFunc() 中,异常捕捉必须使用 defer 和 recover() 实现,当 panic() 抛出异常的时候,recover() 自动捕捉和处理异常信息,使程序能继续往下执行。
- recover() 的返回值是异常信息,如果没有捕捉异常,返回值为空值(nil)。如果能捕捉异常,则返回值为 panic() 设置的异常信息。换句话说,当使用 panic() 抛出异常的时候,panic() 的参数值将会作为 recover() 的返回值。
- 通过 recover() 的返回值判断当前程序是否出现异常,从而执行不同的操作。换句话说,使用 defer、recover() 和 panic() 可以实现代码的流程控制,但编写的代码量较多,而且不符合 Go 语言的设计思想,在开发中不建议使用。
- 如果将 panic() 放在主函数 main() 中,程序也会因异常而终止运行,因为 recover() 的作用域只在函数 myFunc() 中,而 panic() 已超出 recover() 的作用域,所以异常无法成功捕捉。
综上所述,defer、recover() 和 panic() 的关系说明如下:
- defer 具有延时执行的功能,在一个函数中,它的执行顺序在函数返回值之前。
- recover() 捕捉异常,必须与 defer 搭配使用,否则程序无法捕捉异常。
- 如果 panic() 没有搭配 recover(),程序会提示异常而终止运行,如果两者在同一个函数中,程序出现异常仍能继续往下执行。