You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang Gin应用中如何捕获Goroutine内Panic并返回500错误?

Gin中处理异步Goroutine panic并返回HTTP 500的方案

在Gin应用里,异步Goroutine的panic不会自动传递到处理请求的主Goroutine,而且Gin的上下文(*gin.Context)不是并发安全的,不能直接在子Goroutine里调用响应方法。要实现"任意一个Calculate() panic就返回500"的需求,核心思路是用错误通道捕获panic,在主Goroutine里监听错误并及时返回响应。

具体实现步骤

  1. 为计算结果和错误分别创建通道,错误通道要设置足够缓冲避免Goroutine阻塞泄漏
  2. 给每个执行Calculate()的Goroutine添加defer recover()逻辑,将panic转为错误发送到错误通道
  3. 主Goroutine通过select同时监听结果和错误通道,一旦收到错误立即返回500状态码;所有计算正常完成则返回结果

完整代码示例

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

// 假设的物流选项结构体
type LogisticsOption struct {
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

// 模拟可能panic的计算函数
func CalculateOption1() (LogisticsOption, error) {
    // 这里可以模拟panic场景,比如:panic("divide by zero")
    return LogisticsOption{Name: "顺丰速运", Price: 23.5}, nil
}

func CalculateOption2() (LogisticsOption, error) {
    // 模拟panic
    // panic("database connection failed")
    return LogisticsOption{Name: "京东物流", Price: 19.9}, nil
}

func LogisticsHandler(c *gin.Context) {
    // 定义结果通道和错误通道,缓冲数等于Goroutine数量
    resChan := make(chan LogisticsOption, 2)
    errChan := make(chan error, 2)

    // 封装带panic捕获的计算逻辑
    safeRun := func(calcFunc func() (LogisticsOption, error)) {
        defer func() {
            if r := recover(); r != nil {
                // 将panic转为可传递的错误
                errChan <- fmt.Errorf("calculation failed unexpectedly: %v", r)
            }
        }()
        opt, err := calcFunc()
        if err != nil {
            errChan <- err
            return
        }
        resChan <- opt
    }

    // 启动两个异步计算Goroutine
    go safeRun(CalculateOption1)
    go safeRun(CalculateOption2)

    // 收集结果或处理错误
    var results []LogisticsOption
    for i := 0; i < 2; i++ {
        select {
        case err := <-errChan:
            // 任意一个计算出错(包括panic),立即返回500
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            // 关闭通道避免Goroutine泄漏
            close(resChan)
            close(errChan)
            return
        case res := <-resChan:
            results = append(results, res)
        }
    }

    // 所有计算正常完成,返回结果
    c.JSON(http.StatusOK, gin.H{"logistics_options": results})
}

func main() {
    r := gin.Default()
    r.GET("/logistics", LogisticsHandler)
    r.Run(":8080")
}

关键注意事项

  • 错误通道必须设置缓冲,否则当panic发生时,子Goroutine发送错误会阻塞,导致Goroutine泄漏
  • 绝对不能在子Goroutine中直接操作*gin.Context,所有响应逻辑必须回到主请求Goroutine处理
  • 一旦返回错误,要主动关闭剩余通道,避免未完成的Goroutine一直阻塞等待发送数据

内容的提问来源于stack exchange,提问作者Smirnov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 22:02:03