Go语言中的panic是一种用于报告不可恢复错误的机制,而recover则用于捕获panic并尝试恢复程序的正常执行。与通过函数返回值传递的error不同,panic会立即中断当前函数的正常流程,并开始沿调用栈向上传播,执行每个函数的defer语句,直到程序崩溃或被recover捕获。
error处理可预见的错误,而将panic保留给那些不应该发生的、表示程序bug或无法恢复的严重问题(如数组越界、空指针解引用等)。
调用内置函数panic(interface{})会立即停止当前函数的执行,并开始沿着调用栈展开。
package main
import "fmt"
func main() {
fmt.Println("start")
panic("发生了严重错误")
fmt.Println("end") // 不会执行
}
运行时会输出panic信息及堆栈,并以非零状态码退出。
panic会沿着调用栈向上传播,沿途执行每一层的defer语句。
func level1() {
defer fmt.Println("level1 defer")
level2()
}
func level2() {
defer fmt.Println("level2 defer")
level3()
}
func level3() {
defer fmt.Println("level3 defer")
panic("panic in level3")
}
func main() {
level1()
}
// 输出:
// level3 defer
// level2 defer
// level1 defer
// panic: panic in level3
// ...堆栈信息
recover是内置函数,用于在defer函数中捕获panic。如果当前有panic,则返回panic的值并停止恐慌传播;否则返回nil。
func safeDivision(a, b int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到panic:", r)
}
}()
fmt.Println(a / b)
}
func main() {
safeDivision(10, 2) // 5
safeDivision(4, 0) // 捕获到panic: runtime error: integer divide by zero
fmt.Println("程序继续执行")
}
recover必须直接在defer函数中调用才有效。如果嵌套在其他函数中,无法捕获。
// 错误示例:recover不在defer的直接函数中
func tryRecover() {
recover() // 无效,没有panic时返回nil
}
func main() {
defer tryRecover()
panic("oops") // 仍然会崩溃
}
runtime/debug包中的PrintStack()或Stack()可以在recover后输出堆栈信息。
import "runtime/debug"
func safeCall(fn func()) {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到panic:", r)
debug.PrintStack() // 打印堆栈
}
}()
fn()
}
panic表示程序已经处于无法继续安全运行的状态。func initDatabase() *sql.DB {
db, err := sql.Open("driver", "dsn")
if err != nil {
panic("数据库连接失败: " + err.Error())
}
return db
}
使用testing包的defer+recover检测代码是否触发panic。
func TestPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("期望panic但没有发生")
}
}()
// 调用应该panic的函数
dangerousFunc()
}
func worker() {
defer func() {
if r := recover(); r != nil {
log.Printf("worker panic: %v\n堆栈: %s", r, debug.Stack())
}
}()
// 工作逻辑...
}
panic,能返回error就返回error。main函数中,可以设置顶级的recover来避免整个程序意外退出,但要记录详细日志。defer recover之外使用recover。package main
import (
"fmt"
"log"
"net/http"
"runtime/debug"
)
func recoveryMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic recovered: %v\n%s", err, debug.Stack())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next(w, r)
}
}
func riskyHandler(w http.ResponseWriter, r *http.Request) {
// 故意触发panic
var arr []int
fmt.Fprintln(w, arr[0]) // panic: index out of range
}
func main() {
http.HandleFunc("/", recoveryMiddleware(riskyHandler))
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
访问 http://localhost:8080 时,服务器不会崩溃,而是返回500错误并记录堆栈。
panic和recover是Go语言为处理严重错误提供的特殊机制,它们不是常规错误处理工具。正确理解和使用它们,可以编写出更健壮的、能够优雅处理意外情况的程序。下一章我们将学习Go语言的测试与基准测试。