Skip to content

panic和recover机制详解 - Golang基础面试题

panic和recover是Go语言中处理严重错误的机制,用于处理不可恢复的错误情况。本章深入探讨这两个机制的工作原理和正确使用方法。

📋 重点面试题

面试题 1:panic的基本概念和触发条件

难度级别:⭐⭐⭐
考察范围:异常处理/程序控制流
技术标签panic runtime error program termination stack unwinding

问题分析

panic是Go语言中表示程序遇到严重错误而无法继续执行的机制,理解panic的触发条件和执行流程对于编写健壮的Go程序至关重要。

详细解答

1. panic的基本概念和触发方式

点击查看完整代码实现
点击查看完整代码实现
go
package main

import (
    "fmt"
    "runtime"
)

func demonstrateBasicPanic() {
    fmt.Println("=== panic基本概念 ===")
    
    // 1. 手动触发panic
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("捕获panic: %v\n", r)
        }
    }()
    
    fmt.Println("程序开始执行")
    
    // 这会触发panic
    // panic("这是一个手动触发的panic")
    
    fmt.Println("这行代码不会执行") // 不会执行
}

func demonstrateRuntimePanics() {
    fmt.Println("\n=== 运行时panic ===")
    
    // 1. 数组/切片越界访问
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("数组越界panic: %v\n", r)
            }
        }()
        
        arr := [3]int{1, 2, 3}
        fmt.Printf("访问arr[5]: %d\n", arr[5]) // 会panic
    }()
    
    // 2. 空指针解引用
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("空指针panic: %v\n", r)
            }
        }()
        
        var p *int
        fmt.Printf("解引用空指针: %d\n", *p) // 会panic
    }()
    
    // 3. 类型断言失败
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("类型断言panic: %v\n", r)
            }
        }()
        
        var i interface{} = "string"
        num := i.(int) // 会panic
        fmt.Printf("断言结果: %d\n", num)
    }()
    
    // 4. 向已关闭的channel发送数据
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("关闭channel panic: %v\n", r)
            }
        }()
        
        ch := make(chan int)
        close(ch)
        ch <- 1 // 会panic
    }()
    
    // 5. 除零操作(整数)
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("除零panic: %v\n", r)
            }
        }()
        
        var a, b int = 10, 0
        result := a / b // 会panic
        fmt.Printf("除法结果: %d\n", result)
    }()
}

:::

2. panic的执行流程和栈展开

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstratePanicFlow() {
    fmt.Println("\n=== panic执行流程 ===")
    
    fmt.Println("主函数开始")
    
    defer func() {
        fmt.Println("主函数defer 1")
    }()
    
    defer func() {
        fmt.Println("主函数defer 2")
        if r := recover(); r != nil {
            fmt.Printf("在主函数中捕获panic: %v\n", r)
        }
    }()
    
    level1()
    
    fmt.Println("主函数结束") // 不会执行
}

func level1() {
    fmt.Println("  level1开始")
    
    defer func() {
        fmt.Println("  level1 defer 1")
    }()
    
    defer func() {
        fmt.Println("  level1 defer 2")
    }()
    
    level2()
    
    fmt.Println("  level1结束") // 不会执行
}

func level2() {
    fmt.Println("    level2开始")
    
    defer func() {
        fmt.Println("    level2 defer 1")
    }()
    
    defer func() {
        fmt.Println("    level2 defer 2")
    }()
    
    level3()
    
    fmt.Println("    level2结束") // 不会执行
}

func level3() {
    fmt.Println("      level3开始")
    
    defer func() {
        fmt.Println("      level3 defer 1")
    }()
    
    defer func() {
        fmt.Println("      level3 defer 2")
    }()
    
    panic("在level3中发生panic")
    
    fmt.Println("      level3结束") // 不会执行
}

::: :::

3. panic值的类型和信息

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstratePanicValues() {
    fmt.Println("\n=== panic值类型 ===")
    
    // panic可以接受任何类型的值
    testPanics := []interface{}{
        "字符串panic",
        42,
        3.14,
        true,
        []int{1, 2, 3},
        map[string]int{"error": 500},
        struct{ Message string }{"结构体panic"},
        fmt.Errorf("error类型panic"),
    }
    
    for i, panicValue := range testPanics {
        func() {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Printf("panic %d: 类型=%T, 值=%v\n", i+1, r, r)
                }
            }()
            
            panic(panicValue)
        }()
    }
}

::: :::

面试题 2:recover的使用和限制

难度级别:⭐⭐⭐⭐
考察范围:异常恢复/defer机制
技术标签recover defer panic recovery function boundaries

问题分析

recover只能在defer函数中使用,且有严格的使用限制。理解这些限制对于正确使用recover至关重要。

详细解答

1. recover的正确使用方式

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstrateCorrectRecover() {
    fmt.Println("\n=== recover正确使用 ===")
    
    // 1. 直接在defer中使用recover
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("方式1 - 直接recover: %v\n", r)
            }
        }()
        
        panic("测试panic 1")
    }()
    
    // 2. 在defer调用的函数中使用recover
    func() {
        defer handlePanic("方式2")
        panic("测试panic 2")
    }()
    
    // 3. 获取panic信息并转换类型
    func() {
        defer func() {
            if r := recover(); r != nil {
                switch v := r.(type) {
                case string:
                    fmt.Printf("方式3 - 字符串panic: %s\n", v)
                case error:
                    fmt.Printf("方式3 - 错误panic: %v\n", v)
                case int:
                    fmt.Printf("方式3 - 整数panic: %d\n", v)
                default:
                    fmt.Printf("方式3 - 其他类型panic: %v (%T)\n", v, v)
                }
            }
        }()
        
        panic("测试字符串panic")
    }()
}

func handlePanic(context string) {
    if r := recover(); r != nil {
        fmt.Printf("%s - 函数recover: %v\n", context, r)
    }
}

func demonstrateIncorrectRecover() {
    fmt.Println("\n=== recover错误使用 ===")
    
    // 错误方式1:不在defer中使用recover
    func() {
        defer func() {
            fmt.Println("这个defer会执行,但不会捕获panic")
        }()
        
        // 这样不会捕获panic
        if r := recover(); r != nil {
            fmt.Printf("这不会捕获panic: %v\n", r)
        }
        
        // panic("这个panic不会被捕获")
    }()
    
    // 错误方式2:在普通函数中调用recover
    func() {
        defer func() {
            attemptRecover() // 这不会捕获panic
            if r := recover(); r != nil {
                fmt.Printf("正确的recover: %v\n", r)
            }
        }()
        
        panic("测试错误recover")
    }()
    
    // 错误方式3:间接调用recover
    func() {
        defer func() {
            indirectRecover() // 这不会捕获panic
        }()
        
        // panic("间接recover测试")
    }()
}

func attemptRecover() {
    // 这不会捕获任何panic,因为不是在defer的直接调用中
    if r := recover(); r != nil {
        fmt.Printf("错误的recover方式: %v\n", r)
    } else {
        fmt.Println("attemptRecover: 没有捕获到panic")
    }
}

func indirectRecover() {
    // 即使在这里defer也不会捕获上级的panic
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("间接defer recover: %v\n", r)
        }
    }()
    fmt.Println("indirectRecover执行")
}

::: :::

2. recover的有效性规则

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstrateRecoverRules() {
    fmt.Println("\n=== recover有效性规则 ===")
    
    // 规则1:只能在defer函数中使用
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("规则1 - defer中的recover有效: %v\n", r)
            }
        }()
        panic("规则1测试")
    }()
    
    // 规则2:必须在panic的同一个goroutine中
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("规则2 - 同一goroutine的recover: %v\n", r)
            }
        }()
        
        // 在另一个goroutine中的panic不会被捕获
        done := make(chan bool)
        go func() {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Printf("规则2 - goroutine内部recover: %v\n", r)
                }
                done <- true
            }()
            panic("另一个goroutine的panic")
        }()
        
        <-done
        fmt.Println("规则2 - 主goroutine继续执行")
    }()
    
    // 规则3:recover返回值的含义
    func() {
        // 没有panic时,recover返回nil
        defer func() {
            r := recover()
            fmt.Printf("规则3 - 无panic时recover返回: %v (%T)\n", r, r)
        }()
        
        fmt.Println("规则3 - 正常执行,无panic")
    }()
}

::: :::

3. 复杂的recover场景

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstrateComplexRecoverScenarios() {
    fmt.Println("\n=== 复杂recover场景 ===")
    
    // 场景1:多重defer和recover
    func() {
        defer func() {
            fmt.Println("最外层defer开始")
            if r := recover(); r != nil {
                fmt.Printf("最外层recover: %v\n", r)
            }
            fmt.Println("最外层defer结束")
        }()
        
        defer func() {
            fmt.Println("中间层defer开始")
            // 这里不recover,让panic继续传播
            fmt.Println("中间层defer结束")
        }()
        
        defer func() {
            fmt.Println("内层defer开始")
            // 在这里也可以recover
            // if r := recover(); r != nil {
            //     fmt.Printf("内层recover: %v\n", r)
            //     return // 如果这里recover,外层就不会再收到panic
            // }
            fmt.Println("内层defer结束")
        }()
        
        panic("多重defer测试")
    }()
    
    // 场景2:recover后重新panic
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("外层捕获重新panic: %v\n", r)
            }
        }()
        
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("内层捕获原始panic: %v\n", r)
                // 处理panic,然后重新panic
                panic(fmt.Sprintf("处理后重新panic: %v", r))
            }
        }()
        
        panic("原始panic")
    }()
    
    // 场景3:条件性recover
    func() {
        defer conditionalRecover(true)
        panic("条件recover测试1")
    }()
    
    func() {
        defer conditionalRecover(false)
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("备用recover: %v\n", r)
            }
        }()
        panic("条件recover测试2")
    }()
}

func conditionalRecover(shouldRecover bool) {
    if r := recover(); r != nil {
        if shouldRecover {
            fmt.Printf("条件recover - 处理panic: %v\n", r)
        } else {
            fmt.Printf("条件recover - 不处理panic: %v\n", r)
            panic(r) // 重新抛出panic
        }
    }
}

::: :::

面试题 3:panic和defer的交互

难度级别:⭐⭐⭐⭐
考察范围:控制流程/资源管理
技术标签defer execution order panic propagation resource cleanup LIFO order

问题分析

理解panic和defer的交互机制对于正确的资源管理和错误处理至关重要,特别是defer的执行顺序和时机。

详细解答

1. defer的执行顺序

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
import (
    "os"
    "time"
)

func demonstrateDeferOrder() {
    fmt.Println("\n=== defer执行顺序 ===")
    
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("最后执行的defer捕获panic: %v\n", r)
            }
        }()
        
        defer fmt.Println("defer 1 (最后注册,最先执行)")
        defer fmt.Println("defer 2")
        defer fmt.Println("defer 3")
        defer fmt.Println("defer 4")
        defer fmt.Println("defer 5 (最先注册,最后执行)")
        
        fmt.Println("正常代码执行")
        panic("测试defer顺序")
    }()
}

// 资源清理示例
func demonstrateResourceCleanup() {
    fmt.Println("\n=== 资源清理示例 ===")
    
    processFileWithPanic := func(filename string) (err error) {
        // 捕获panic并转换为error
        defer func() {
            if r := recover(); r != nil {
                err = fmt.Errorf("处理文件时发生panic: %v", r)
            }
        }()
        
        // 模拟打开文件
        file, err := os.Create(filename)
        if err != nil {
            return fmt.Errorf("创建文件失败: %w", err)
        }
        
        // 确保文件被关闭
        defer func() {
            if closeErr := file.Close(); closeErr != nil {
                fmt.Printf("关闭文件失败: %v\n", closeErr)
            } else {
                fmt.Printf("文件 %s 已安全关闭\n", filename)
            }
        }()
        
        // 清理临时文件
        defer func() {
            if removeErr := os.Remove(filename); removeErr != nil {
                fmt.Printf("删除临时文件失败: %v\n", removeErr)
            } else {
                fmt.Printf("临时文件 %s 已清理\n", filename)
            }
        }()
        
        // 模拟文件操作
        _, err = file.WriteString("测试内容")
        if err != nil {
            return fmt.Errorf("写入文件失败: %w", err)
        }
        
        // 模拟可能的panic
        if filename == "panic.txt" {
            panic("模拟文件处理panic")
        }
        
        return nil
    }
    
    // 测试正常情况
    if err := processFileWithPanic("normal.txt"); err != nil {
        fmt.Printf("处理normal.txt失败: %v\n", err)
    } else {
        fmt.Println("处理normal.txt成功")
    }
    
    // 测试panic情况
    if err := processFileWithPanic("panic.txt"); err != nil {
        fmt.Printf("处理panic.txt失败: %v\n", err)
    } else {
        fmt.Println("处理panic.txt成功")
    }
}

::: :::

2. 复杂的defer和panic交互

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstrateComplexDeferPanic() {
    fmt.Println("\n=== 复杂defer-panic交互 ===")
    
    // 场景1:defer中修改返回值
    result := func() (result string) {
        defer func() {
            if r := recover(); r != nil {
                result = fmt.Sprintf("panic处理结果: %v", r)
            }
        }()
        
        defer func() {
            result += " [defer修改]"
        }()
        
        result = "正常结果"
        panic("测试defer修改返回值")
        return result
    }()
    
    fmt.Printf("函数返回值: %s\n", result)
    
    // 场景2:defer中的panic
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("外层recover: %v\n", r)
            }
        }()
        
        defer func() {
            fmt.Println("这个defer会执行")
            panic("defer中的panic") // 会覆盖原始panic
        }()
        
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("中间recover: %v\n", r)
                // 不重新panic,所以原始panic被"吞掉"了
            }
        }()
        
        panic("原始panic")
    }()
    
    // 场景3:多个defer中的panic
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("最终recover: %v\n", r)
            }
        }()
        
        defer func() {
            panic("第三个panic")
        }()
        
        defer func() {
            panic("第二个panic")
        }()
        
        defer func() {
            panic("第一个panic")
        }()
        
        panic("原始panic")
    }()
}

::: :::

3. defer性能和最佳实践

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstrateDeferPerformance() {
    fmt.Println("\n=== defer性能考虑 ===")
    
    // 性能测试:defer vs 直接调用
    const iterations = 1000000
    
    // 测试直接调用
    start := time.Now()
    for i := 0; i < iterations; i++ {
        directCall()
    }
    directTime := time.Since(start)
    
    // 测试defer调用
    start = time.Now()
    for i := 0; i < iterations; i++ {
        deferCall()
    }
    deferTime := time.Since(start)
    
    fmt.Printf("直接调用耗时: %v\n", directTime)
    fmt.Printf("defer调用耗时: %v\n", deferTime)
    fmt.Printf("defer开销: %.2fx\n", float64(deferTime)/float64(directTime))
    
    // defer的最佳实践
    demonstrateDeferBestPractices()
}

func directCall() {
    // 模拟简单操作
    _ = 1 + 1
}

func deferCall() {
    defer func() {
        _ = 1 + 1
    }()
}

func demonstrateDeferBestPractices() {
    fmt.Println("\n=== defer最佳实践 ===")
    
    // 1. 尽早设置defer
    func() {
        file, err := os.Create("example.txt")
        if err != nil {
            fmt.Printf("创建文件失败: %v\n", err)
            return
        }
        defer file.Close() // 立即设置defer
        defer os.Remove("example.txt") // 清理文件
        
        // 文件操作...
        file.WriteString("示例内容")
        
        fmt.Println("文件操作完成")
    }()
    
    // 2. 避免在循环中使用defer
    processFiles := func(filenames []string) {
        for _, filename := range filenames {
            // 错误方式:defer会积累
            // file, _ := os.Open(filename)
            // defer file.Close()  // 所有defer会在函数结束时执行
            
            // 正确方式:使用匿名函数
            func() {
                file, err := os.Create(filename)
                if err != nil {
                    return
                }
                defer file.Close()
                defer os.Remove(filename)
                
                // 处理文件...
                file.WriteString("内容")
            }()
        }
    }
    
    testFiles := []string{"test1.txt", "test2.txt", "test3.txt"}
    processFiles(testFiles)
    
    // 3. defer与命名返回值
    result := func() (result int, err error) {
        defer func() {
            if err != nil {
                result = -1 // defer中可以修改命名返回值
            }
        }()
        
        // 模拟可能出错的操作
        if time.Now().Unix()%2 == 0 {
            return 42, nil
        }
        return 0, errors.New("模拟错误")
    }()
    
    fmt.Printf("函数结果: %d\n", result)
}

::: :::

面试题 4:panic和recover的使用场景和最佳实践

难度级别:⭐⭐⭐⭐⭐
考察范围:设计原则/错误处理策略
技术标签panic usage graceful degradation library design error boundaries

问题分析

panic和recover的正确使用对程序稳定性至关重要,需要了解何时使用panic,如何设计错误边界。

详细解答

1. panic的适用场景

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstratePanicUseCases() {
    fmt.Println("\n=== panic适用场景 ===")
    
    // 1. 程序初始化失败
    initializeApplication := func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("应用初始化失败: %v\n", r)
                // 记录日志,通知运维等
            }
        }()
        
        // 配置文件加载
        if err := loadConfig(); err != nil {
            panic(fmt.Sprintf("配置文件加载失败: %v", err))
        }
        
        // 数据库连接
        if err := connectDatabase(); err != nil {
            panic(fmt.Sprintf("数据库连接失败: %v", err))
        }
        
        fmt.Println("应用初始化成功")
    }
    
    initializeApplication()
    
    // 2. 不可能发生的情况
    processValue := func(value string) {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("处理意外情况: %v\n", r)
            }
        }()
        
        switch value {
        case "A":
            fmt.Println("处理A")
        case "B":
            fmt.Println("处理B")
        case "C":
            fmt.Println("处理C")
        default:
            // 如果代码逻辑保证只会传入A、B、C,这种情况应该不会发生
            panic(fmt.Sprintf("不支持的值: %s", value))
        }
    }
    
    processValue("A")
    processValue("B")
    processValue("INVALID") // 触发panic
    
    // 3. 库的边界检查
    mathLibrary := struct {
        Factorial func(n int) int
    }{
        Factorial: func(n int) (result int) {
            defer func() {
                if r := recover(); r != nil {
                    // 将panic转换为合理的错误处理
                    result = -1
                    fmt.Printf("阶乘计算出错: %v\n", r)
                }
            }()
            
            if n < 0 {
                panic("阶乘不能计算负数")
            }
            if n > 20 {
                panic("阶乘参数过大,可能溢出")
            }
            
            result = 1
            for i := 2; i <= n; i++ {
                result *= i
            }
            return result
        },
    }
    
    fmt.Printf("5! = %d\n", mathLibrary.Factorial(5))
    fmt.Printf("(-1)! = %d\n", mathLibrary.Factorial(-1))
    fmt.Printf("25! = %d\n", mathLibrary.Factorial(25))
}

func loadConfig() error {
    // 模拟配置加载
    return nil
}

func connectDatabase() error {
    // 模拟数据库连接
    return nil
}

::: :::

2. 错误边界和panic隔离

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
// HTTP服务器错误边界示例
type SafeHandler struct {
    handler func(string) string
}

func (sh *SafeHandler) Handle(request string) (response string) {
    defer func() {
        if r := recover(); r != nil {
            response = fmt.Sprintf("内部服务器错误: %v", r)
            fmt.Printf("处理请求时发生panic: %v\n", r)
            
            // 记录详细的错误信息
            buf := make([]byte, 1024)
            n := runtime.Stack(buf, false)
            fmt.Printf("堆栈信息:\n%s\n", buf[:n])
        }
    }()
    
    return sh.handler(request)
}

func demonstrateErrorBoundaries() {
    fmt.Println("\n=== 错误边界 ===")
    
    // 创建可能panic的处理器
    panicHandler := &SafeHandler{
        handler: func(request string) string {
            switch request {
            case "panic":
                panic("模拟处理器panic")
            case "error":
                return "处理出错"
            default:
                return fmt.Sprintf("处理请求: %s", request)
            }
        },
    }
    
    // 测试不同类型的请求
    requests := []string{"normal", "error", "panic", "another"}
    
    for _, req := range requests {
        response := panicHandler.Handle(req)
        fmt.Printf("请求: %s, 响应: %s\n", req, response)
    }
}

::: :::

3. 优雅的panic处理策略

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
// 工作池中的panic处理
type WorkerPool struct {
    workers   int
    tasks     chan func()
    quit      chan bool
    panicHandler func(interface{})
}

func NewWorkerPool(workers int) *WorkerPool {
    return &WorkerPool{
        workers: workers,
        tasks:   make(chan func(), 100),
        quit:    make(chan bool),
        panicHandler: func(r interface{}) {
            fmt.Printf("工作线程panic: %v\n", r)
            // 可以在这里记录日志、发送告警等
        },
    }
}

func (wp *WorkerPool) Start() {
    for i := 0; i < wp.workers; i++ {
        go wp.worker(i)
    }
}

func (wp *WorkerPool) worker(id int) {
    defer func() {
        if r := recover(); r != nil {
            if wp.panicHandler != nil {
                wp.panicHandler(r)
            }
            // 重启worker
            go wp.worker(id)
        }
    }()
    
    for {
        select {
        case task := <-wp.tasks:
            func() {
                defer func() {
                    if r := recover(); r != nil {
                        fmt.Printf("工作线程 %d 任务panic: %v\n", id, r)
                        // 任务级别的panic处理,不影响worker
                    }
                }()
                task()
            }()
        case <-wp.quit:
            fmt.Printf("工作线程 %d 退出\n", id)
            return
        }
    }
}

func (wp *WorkerPool) Submit(task func()) {
    wp.tasks <- task
}

func (wp *WorkerPool) Stop() {
    close(wp.quit)
}

func demonstrateGracefulPanicHandling() {
    fmt.Println("\n=== 优雅的panic处理 ===")
    
    pool := NewWorkerPool(3)
    pool.Start()
    
    // 提交正常任务
    pool.Submit(func() {
        fmt.Println("执行正常任务")
        time.Sleep(100 * time.Millisecond)
    })
    
    // 提交会panic的任务
    pool.Submit(func() {
        fmt.Println("执行会panic的任务")
        panic("任务执行失败")
    })
    
    // 提交更多正常任务
    for i := 0; i < 5; i++ {
        i := i // 捕获循环变量
        pool.Submit(func() {
            fmt.Printf("执行任务 %d\n", i)
            time.Sleep(50 * time.Millisecond)
        })
    }
    
    // 等待任务完成
    time.Sleep(1 * time.Second)
    
    pool.Stop()
    time.Sleep(100 * time.Millisecond) // 等待worker退出
}

::: :::

4. panic vs error的选择原则

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstratePanicVsError() {
    fmt.Println("\n=== panic vs error选择 ===")
    
    // 使用error的情况(推荐)
    divide := func(a, b float64) (float64, error) {
        if b == 0 {
            return 0, errors.New("除数不能为零")
        }
        return a / b, nil
    }
    
    // 使用panic的情况(谨慎使用)
    mustDivide := func(a, b float64) float64 {
        if b == 0 {
            panic("除数不能为零")
        }
        return a / b
    }
    
    // error方式调用
    if result, err := divide(10, 2); err != nil {
        fmt.Printf("除法错误: %v\n", err)
    } else {
        fmt.Printf("除法结果: %.2f\n", result)
    }
    
    if result, err := divide(10, 0); err != nil {
        fmt.Printf("除法错误: %v\n", err)
    } else {
        fmt.Printf("除法结果: %.2f\n", result)
    }
    
    // panic方式调用(需要recover)
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("捕获panic: %v\n", r)
            }
        }()
        
        result := mustDivide(10, 2)
        fmt.Printf("Must除法结果: %.2f\n", result)
        
        result = mustDivide(10, 0) // 会panic
        fmt.Printf("这行不会执行: %.2f\n", result)
    }()
    
    // 选择原则演示
    demonstrateChoicePrinciples()
}

func demonstrateChoicePrinciples() {
    fmt.Println("\n=== 选择原则 ===")
    
    // 1. 可预期的错误 -> 使用error
    validateEmail := func(email string) error {
        if email == "" {
            return errors.New("邮箱不能为空")
        }
        if !strings.Contains(email, "@") {
            return errors.New("邮箱格式无效")
        }
        return nil
    }
    
    // 2. 程序员错误 -> 使用panic
    getArrayElement := func(arr []int, index int) int {
        if index < 0 || index >= len(arr) {
            panic(fmt.Sprintf("数组越界: index=%d, length=%d", index, len(arr)))
        }
        return arr[index]
    }
    
    // 3. 系统资源错误 -> 根据情况选择
    openFile := func(filename string) (*os.File, error) {
        file, err := os.Open(filename)
        if err != nil {
            // 文件不存在是可预期的,返回error
            return nil, fmt.Errorf("打开文件失败: %w", err)
        }
        return file, nil
    }
    
    mustLoadConfig := func(filename string) map[string]string {
        // 配置文件是程序运行的前提,加载失败应该panic
        content, err := os.ReadFile(filename)
        if err != nil {
            panic(fmt.Sprintf("配置文件加载失败: %v", err))
        }
        
        config := make(map[string]string)
        // 解析配置...
        return config
    }
    
    // 测试使用
    if err := validateEmail("test@example.com"); err != nil {
        fmt.Printf("邮箱验证失败: %v\n", err)
    } else {
        fmt.Println("邮箱验证通过")
    }
    
    if err := validateEmail("invalid-email"); err != nil {
        fmt.Printf("邮箱验证失败: %v\n", err)
    }
    
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("数组访问panic: %v\n", r)
            }
        }()
        
        arr := []int{1, 2, 3}
        fmt.Printf("数组元素: %d\n", getArrayElement(arr, 1))
        fmt.Printf("数组元素: %d\n", getArrayElement(arr, 5)) // panic
    }()
    
    if file, err := openFile("nonexistent.txt"); err != nil {
        fmt.Printf("文件操作错误: %v\n", err)
    } else {
        file.Close()
    }
    
    // 配置加载(通常在程序启动时)
    func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("配置加载失败: %v\n", r)
            }
        }()
        
        config := mustLoadConfig("nonexistent-config.txt")
        fmt.Printf("配置加载成功: %v\n", config)
    }()
}

::: :::

🎯 核心知识点总结

panic基础要点

  1. 触发条件: 手动调用panic()、运行时错误(如数组越界、空指针等)
  2. 执行流程: panic会终止当前函数执行,开始栈展开过程
  3. panic值: 可以是任何类型的值,通常是字符串或error
  4. 程序终止: 如果没有recover,程序会打印堆栈信息并终止

recover基础要点

  1. 使用限制: 只能在defer函数中直接调用才有效
  2. 返回值: panic时返回panic的值,无panic时返回nil
  3. goroutine限制: 只能recover同一goroutine中的panic
  4. 调用时机: 必须在panic发生后的defer执行阶段

defer交互要点

  1. 执行顺序: defer按LIFO(后进先出)顺序执行
  2. 资源清理: 即使发生panic,defer也会执行,适合资源清理
  3. 返回值修改: defer可以修改命名返回值
  4. 性能考虑: defer有一定性能开销,但通常可以忽略

使用原则要点

  1. panic场景: 程序初始化失败、不可能发生的情况、库的边界检查
  2. error优先: 大多数情况下应该使用error而不是panic
  3. 错误边界: 在适当的层次设置recover,避免panic传播
  4. 优雅处理: 在生产环境中要有完善的panic处理和恢复机制

🔍 面试准备建议

  1. 理解基本概念: 深入理解panic和recover的工作机制
  2. 掌握使用场景: 知道什么时候使用panic,什么时候使用error
  3. 熟悉限制条件: 了解recover的各种限制和有效性规则
  4. 实践最佳实践: 掌握错误边界设计和优雅的panic处理
  5. 性能意识: 了解defer和panic对性能的影响

正在精进