gin默认为每个请求启动独立goroutine,天然支持高并发;但直接在handler中用go启动子goroutine易致协程泛滥、上下文失效、panic失控等问题,生产环境应使用ants等协程池管控异步任务。

Gin 本身不提供协程池,直接用 go 启动 goroutine 处理请求,在高并发下极易导致 goroutine 泛滥、内存暴涨甚至 OOM。真正在生产环境扛住高并发的方案,是把耗时操作(如日志落盘、消息推送、第三方调用)从主请求流程中剥离,并交由可控的协程池执行。
为什么不能直接在 Gin handler 里写 go doSomething()
看似简单,实则危险:
- 没有数量限制:1000 并发请求 → 可能瞬间 spawn 1000+ goroutine,调度开销剧增
- 无生命周期管理:goroutine 执行完就退出,但若它内部又启了子 goroutine 或用了
time.After,容易泄漏 - 错误不可捕获:panic 会直接 crash 当前 goroutine,无法被 Gin 的
Recovery()拦截 - 上下文丢失:
c.Request.Context()在 handler 返回后即失效,子 goroutine 若继续用它可能读到已 cancel 的 ctx
用 ants 或 goflow 实现可控协程池
推荐使用 github.com/panjf2000/ants/v2 —— 轻量、稳定、社区活跃,且支持任务超时和拒绝策略。
安装:
go get github.com/panjf2000/ants/v2
初始化协程池(建议在 main() 中全局创建,复用):
var pool *ants.Pool
func init() {
var err error
pool, err = ants.NewPool(100) // 最多 100 个并发 worker
if err != nil {
log.Fatal(err)
}
defer pool.Release() // 服务退出时释放
}
在 handler 中提交异步任务:
r.POST("/notify", func(c *gin.Context) {
userID := c.Param("id")
msg := c.PostForm("msg")
// 提交到协程池,不阻塞主流程
err := pool.Submit(func() {
// 注意:这里不能再用 c,因为 handler 已返回
// 需要拷贝必要参数,或传入独立 context
sendToMQ(userID, msg) // 耗时操作
})
if err != nil {
c.JSON(500, gin.H{"error": "task rejected"})
return
}
c.JSON(202, gin.H{"status": "accepted"})
})
协程池 + Context 传递的正确姿势
如果异步任务需要感知超时或可取消(比如调用下游 HTTP 接口),不能直接传 c.Request.Context(),而应派生一个带 deadline 的子 context:
- 在 handler 内部用
ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second) - 将
ctx和必要参数一起打包进闭包,再提交给协程池 - 务必在 goroutine 结束时调用
cancel(),避免 context 泄漏
示例:
err := pool.Submit(func() {
defer cancel() // 关键:确保 cancel 被调用
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
log.Printf("downstream call failed: %v", err)
return
}
defer resp.Body.Close()
// 处理响应...
})
协程池不是万能的,这些地方千万别用
协程池只适合「非关键路径的异步副作用」,以下场景必须同步处理:
- 数据库写操作(尤其是事务内)—— 异步提交会导致数据不一致或事务失效
- 需要返回结果给客户端的逻辑(如生成 token、校验权限)—— 异步无法把结果塞回
c.JSON() - 依赖当前请求生命周期的资源(如临时文件句柄、TLS 连接)—— handler 返回后资源已被回收
- 高频小任务(如每请求解析一次 JSON)—— 协程调度开销反而高于执行本身
真正难的不是启动多少 goroutine,而是判断哪部分该放进去、哪部分必须留在主流程里。很多线上事故,都源于把「以为可以异步」的操作扔进了池子,却没验证它是否真的与上下文解耦。











