vault backup: 2026-06-07 12:14:39
This commit is contained in:
@@ -1,85 +1,148 @@
|
||||
---
|
||||
tags:
|
||||
- Go
|
||||
- golang
|
||||
- go基础语法
|
||||
- 异常捕获
|
||||
tags: [go, golang, go基础语法, 异常捕获]
|
||||
create time: 2026-06-07 15:00
|
||||
---
|
||||
|
||||
# Go语言异常捕获
|
||||
# Go 语言异常捕获
|
||||
|
||||
## recover异常捕获
|
||||
异常其实就是指程序运行过程中发生了`panic`,那么我们为了不让程序报错退出,可以在程序中加入`recover`机制,将异常捕获,打印出异常,这样也方便我们定位错误。而捕获的方式我们之前在讲`defer`的时候也提到过,一般是用`recover`和`defer`搭配使用来捕获异常。
|
||||
下面请看个具体例子:
|
||||
## 概述
|
||||
|
||||
Go 用 `panic` 表示不可恢复的错误,用 `recover` + `defer` 捕获 panic。本文讲解 panic 的传递规则、recover 的正确用法,以及何时不该用 recover。
|
||||
|
||||
## 正文
|
||||
|
||||
### panic vs error:该用哪个?
|
||||
|
||||
这是 Go 开发者最常见的困惑之一:
|
||||
|
||||
| | error | panic |
|
||||
|--|-------|-------|
|
||||
| 用途 | **预期内**的错误(文件不存在、参数无效) | **不应该发生**的严重错误(nil 指针解引用、数组越界) |
|
||||
| 处理方式 | if err != nil 检查 | defer + recover 捕获 |
|
||||
| 是否继续执行 | 是(处理后可继续) | 否(当前 goroutine 栈展开) |
|
||||
|
||||
> [!tip] 💡 黄金法则
|
||||
> **99% 的场景你应该用 error,而不是 panic。** panic 应该留给"程序状态已损坏、无法安全继续"的情况。
|
||||
|
||||
### recover:唯一的安全网
|
||||
|
||||
```go
|
||||
func main() {
|
||||
defer func() {
|
||||
if error:=recover();error!=nil{
|
||||
fmt.Println("出现了panic,使用reover获取信息:",error)
|
||||
}
|
||||
}()
|
||||
fmt.Println("11111111111")
|
||||
panic("出现panic")
|
||||
fmt.Println("22222222222")
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
11111111111
|
||||
出现了panic,使用reover获取信息: 出现panic
|
||||
```
|
||||
**注意**,这里有了`recover`之后,程序不会在`panic`出中断,再执行完`panic`之后,会接下来执行`defer recover`函数,但是当前函数`panic`后面的代码不会被执行,但是调用该函数的代码会接着执行。
|
||||
如果我们在`main`函数中未加入`defer func(){...}`,当我们的程序运行到底8行时就会`panic`掉,而通常在我们的业务程序中对于程序`panic`是不可容忍的,我们需要程序健壮的运行,而不是是不是因为一些`panic`挂掉又被拉起,所以当发生`panic`的时候我们要让程序能够继续运行,并且获取到发生`panic`的具体错误,这就可以用上述方法。
|
||||
|
||||
## panic传递
|
||||
当一个函数发生了`panic`之后,若在当前函数中没有`recover`,会一直向外层传递直到主函数,如果迟迟没有`recover`的话,那么程序将终止。如果在过程中遇到了最近的`recover`,则将被捕获。
|
||||
看下面例子:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func testPanic1(){
|
||||
fmt.Println("testPanic1上半部分")
|
||||
testPanic2()
|
||||
fmt.Println("testPanic1下半部分")
|
||||
}
|
||||
|
||||
func testPanic2(){
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
fmt.Println("testPanic2上半部分")
|
||||
testPanic3()
|
||||
fmt.Println("testPanic2下半部分")
|
||||
}
|
||||
|
||||
func testPanic3(){
|
||||
fmt.Println("testPanic3上半部分")
|
||||
panic("在testPanic3出现了panic")
|
||||
fmt.Println("testPanic3下半部分")
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("程序开始")
|
||||
testPanic1()
|
||||
fmt.Println("程序结束")
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("捕获到 panic:", r)
|
||||
}
|
||||
}()
|
||||
|
||||
panic("出事了!")
|
||||
fmt.Println("这行不会执行")
|
||||
}
|
||||
// 输出:
|
||||
// 捕获到 panic: 出事了!
|
||||
```
|
||||
|
||||
> [!note] 📝 recover 的关键限制
|
||||
> - `recover()` **只能在 defer 中调用**才有效
|
||||
> - 它恢复的是**当前 goroutine** 的 panic
|
||||
> - 被 recover 后,函数正常返回,调用方继续执行
|
||||
|
||||
### panic 的传递规则
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["testPanic3 触发 panic"] --> B{"有 recover?"}
|
||||
B -->|否| C["栈展开,向上找"]
|
||||
C --> D["testPanic2 有 recover → 捕获"]
|
||||
D --> E["testPanic2 返回"]
|
||||
E --> F["testPanic1 继续执行"]
|
||||
F --> G["main 继续执行"]
|
||||
```
|
||||
|
||||
当 panic 没有被当前函数 recover 时,它会沿调用链向上传播,直到遇到 recover 或到达 `main`:
|
||||
|
||||
```go
|
||||
func testPanic3() {
|
||||
fmt.Println("上半部分")
|
||||
panic("boom!")
|
||||
fmt.Println("下半部分") // ❌ 不会执行
|
||||
}
|
||||
|
||||
func testPanic2() {
|
||||
defer func() { recover() }() // ✅ 捕获了来自 testPanic3 的 panic
|
||||
testPanic3()
|
||||
fmt.Println("testPanic2 继续") // 会执行
|
||||
}
|
||||
|
||||
func testPanic1() {
|
||||
testPanic2()
|
||||
fmt.Println("testPanic1 继续") // 会执行
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
程序开始
|
||||
testPanic1上半部分
|
||||
testPanic2上半部分
|
||||
testPanic3上半部分
|
||||
testPanic1下半部分
|
||||
程序结束
|
||||
```
|
||||
解析:
|
||||
调用链:`main-->testPanic1-->testPanic2-->testPanic3`,但是在`testPanic3`中发现了一个`panic`,由于`testPanic3`没有`recover`,向上找,在`testPanic2`中找到了`recover`,`panic`被捕获了,程序接着运行,由于`testPanic3`发生了`panic`,所以不再继续运行,函数跳出返回到`testPanic2`,`testPanic2`中捕获到了`panic`,也不会再继续执行,跳出函数`testPanic2`,到了`testPanic1`接着运行。
|
||||
|
||||
所以`recover`和`panic`可以总结为以下两点:
|
||||
1. `recover()`只能恢复当前函数级或以当前函数为首的调用链中的函数中的`panic()`,恢复后调用当前函数结束,但是调用此函数的函数继续执行
|
||||
2. 函数发生了`panic`之后会一直向上传递,如果直至`main`函数都没有`recover()`,程序将终止,如果是碰见了`recover()`,将被`recover`捕获。
|
||||
输出:
|
||||
```
|
||||
上半部分
|
||||
testPanic2 继续
|
||||
testPanic1 继续
|
||||
```
|
||||
|
||||
> [!warning] ⚠️ 没有 recover 的后果
|
||||
> 如果 panic 一路传播到 `main` 都没有被 recover,程序会崩溃并打印堆栈信息:
|
||||
> ```
|
||||
> panic: boom!
|
||||
> goroutine 1 [running]:
|
||||
> ...
|
||||
> ```
|
||||
|
||||
### 实战:安全的数学运算
|
||||
|
||||
```go
|
||||
func SafeDivide(a, b int) (result int, errMsg string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errMsg = fmt.Sprintf("除零错误: %v", r)
|
||||
}
|
||||
}()
|
||||
return a / b, ""
|
||||
}
|
||||
|
||||
val, msg := SafeDivide(10, 0)
|
||||
if msg != "" {
|
||||
log.Println(msg) // 除零错误: runtime error: integer divide by zero
|
||||
}
|
||||
```
|
||||
|
||||
### 常见使用场景
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| 服务器中间件 | 捕获请求级 panic,避免整个服务崩溃 |
|
||||
| 并发池 | Worker 协程中 recover,防止单个 goroutine 挂掉影响其他任务 |
|
||||
| 插件系统 | 用户代码可能 panic,需要隔离保护 |
|
||||
|
||||
```go
|
||||
// 并发池中的典型模式
|
||||
func Worker(id int, jobs <-chan int, results chan<- int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("worker %d panicked: %v", id, r)
|
||||
}
|
||||
}()
|
||||
for j := range jobs {
|
||||
results <- process(j)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 关键要点总结
|
||||
|
||||
> [!warning] ⚠️ 不要滥用 recover
|
||||
> - 业务逻辑错误 → 用 error
|
||||
> - 编程错误(bug)→ panic 是可以的
|
||||
> - 第三方库 panic → recover 保护你的程序
|
||||
> - 每个函数都加 recover → 掩盖 bug,不利于调试
|
||||
|
||||
> [!info] ℹ️ 最佳实践
|
||||
> 1. 在入口点(如 HTTP handler、main 函数顶层)加一层 recover
|
||||
> 2. 在并发 goroutine 中加 recover,防止单个协程 crash 扩散
|
||||
> 3. 记录完整的 panic 堆栈(可用 `runtime.Stack()`)后再恢复
|
||||
|
||||
Reference in New Issue
Block a user