不能直接在goroutine里用c,因*gin.context是request-scoped的,handler返回后其底层资源可能被回收或复用,再调用c.param()等方法会返回空值、脏数据或panic;必须提前提取所需数据,禁止传c指针。

为什么不能直接在 goroutine 里用 c?
因为 *gin.Context 是 request-scoped 的,handler 函数返回后,底层的 http.ResponseWriter 和 *http.Request 可能被回收或复用。此时再调用 c.Param()、c.PostForm() 或 c.GetHeader(),轻则返回空值或脏数据,重则 panic。
常见错误现象:异步任务里读到空 user_id、panic: context canceled、日志里反复出现 http: response.WriteHeader on hijacked connection。
- 必须在启动 goroutine 前,把所有需要的数据提取出来(如
c.GetString("uid")、c.MustGet("payload")) - 禁止传
c指针进 goroutine;c.Copy()仅适用于极简单场景(比如只读 URL.Path),且仍不推荐 - 如果要用上下文控制超时,应新建
context.WithTimeout(context.Background(), 30*time.Second),而非复用c.Request.Context()
channel 该不该 close()?
在单次请求生命周期中使用 channel 作结果通知时,close() 不是必须的,但漏关或误关容易引发 panic 或 goroutine 泄漏。
典型错误:多个 goroutine 同时向同一 channel 写入,或写完没关导致接收方永远阻塞;又或者提前 close() 后继续写入,触发 panic: send on closed channel。
- 若 channel 仅用于一次结果传递(如异步任务完成通知),建议用
defer close(ch)放在写入语句后 - 若 channel 被复用(例如全局 worker pool),绝对不要
close(),改用带缓冲的make(chan T, 1)+select非阻塞写入 - 接收方务必用
result, ok := 判断是否已关闭,避免死锁
如何安全地把结果回传给客户端?
不能在后台 goroutine 里调用 c.JSON() 或 c.String() —— 此时 handler 已退出,response writer 已失效。
真正可行的路径只有两条:要么客户端轮询状态接口,要么用长连接(WebSocket / SSE),而 channel 本身只是内部协调工具,不负责 HTTP 响应。
- 推荐做法:立即返回任务 ID(如
c.JSON(202, gin.H{"task_id": "abc123"})),后台任务完成后写入 DB/Redis,由另一个/task/status/:id接口查结果 - 若坚持用 channel 回传响应,必须确保接收 goroutine 在 handler 返回前完成写响应(即不能让
c.JSON()发生在 handler 作用域外) - 示例中常见的
go func() { c.JSON(...) }()是错的 —— 这会 crash,Gin 不允许跨 handler 生命周期操作 response writer
goroutine 泄漏比性能慢更危险
没加超时、没做 recover、没限制并发数的 goroutine,跑着跑着就吃光内存或文件描述符,比响应慢更难排查。
你看到的 “CPU 突然飙高”“服务重启后恢复正常”,大概率是堆积了成千上万个卡在 http.Client.Do() 或数据库查询上的 goroutine。
- 所有外部调用必须设超时:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - 用
sync.WaitGroup或context.WithCancel主动终止长期任务,尤其涉及循环或重试逻辑时 - 避免无限制启动 goroutine:
for range items { go process(item) }应改为带 worker pool 的限流模式
通道不是银弹,它只是同步原语;真正决定系统健壮性的,是你对资源边界和生命周期的敬畏程度。











