轮询接口需用 select + context 安全等待事件,避免 goroutine 泄漏:监听 req.Context().Done()、eventCh 和 time.After(timeout),每次请求新建独立 channel,不可直接 c.JSON + time.Sleep。

轮询接口本身不需要 Gin 特殊支持,但用 Gin 实现时容易误用中间件、上下文或超时机制,导致连接提前断开、goroutine 泄漏或响应延迟。关键不是“怎么写个 GET 接口”,而是“怎么让轮询不退化、不堆积、不卡死”。
为什么不能直接用 c.JSON + time.Sleep 模拟轮询
这种写法看似简单,实则危险:
- 客户端断开后,
time.Sleep仍会执行完,goroutine 不释放——req.Context().Done()被忽略 - 没有超时控制,单次等待可能无限期挂起(比如事件 channel 永远不发)
- 多个请求共用一个全局 channel 时,推送会广播给所有等待者,而非定向唤醒
- Gin 默认启用
Recovery和Logger中间件,它们会在 handler 返回后才刷日志,掩盖了阻塞真实位置
如何用 select + context 安全等待事件
每个请求必须有独立的等待逻辑,靠 select 同时监听三个信号源:
-
req.Context().Done():客户端关闭连接或主动取消 -
eventCh:服务端业务侧推送的数据(建议用chan interface{},每次请求新建) -
time.After(timeout):兜底超时,防止服务端逻辑卡死
示例片段(非完整 handler):
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
done
-
ReadTimeout和WriteTimeout必须设为0,否则底层 net/http 会在超时后强制关连接 - Nginx 反向代理需同步配
proxy_read_timeout 300(至少 ≥ 你的业务超时) - 不要在 handler 里调用
c.Writer.Flush()除非你明确需要流式响应;轮询是单次响应,Flush 反而可能触发中间件提前写 header - 避免在 handler 内启动 goroutine 后不跟踪——比如用
go func(){ ... }()发送事件却没用 sync.WaitGroup 或 channel 等待,极易泄漏
轮询状态如何被前端安全消费
前端不应在上传或操作中途长连接等待,而应使用短轮询查状态:
- 服务端返回唯一
uploadID后,前端发起独立 GET 请求如/api/progress?id=xxx - 服务端用
sync.Map存储各 ID 对应进度,key 是 string,value 是 struct{ Progress int; Done bool } - 不要用 WebSocket 替代——上传是单向流,WebSocket 连接生命周期更脆弱,一次超时就丢失全部进度
- 响应中加
Cache-Control: no-cache头,防代理或浏览器缓存旧进度
真正的难点不在“怎么返回数据”,而在“怎么确保每次轮询都拿到最新、不重复、不遗漏的状态”,这依赖服务端原子更新和客户端幂等轮询节奏。别为了省一次请求,把状态同步逻辑搞复杂。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










