用 time.After 在循环中反复调用会导致 goroutine 泄露,因其每次新建独立计时器,无法复用或取消,与全局倒计时不等价。

Go 里做超时控制,核心就两条路:用 time.After 或用 context.WithTimeout。前者轻量、适合单次简单判断;后者可传播、可取消、适合真实业务链路——选错会导致 goroutine 泄露或超时失效。
用 time.After 做超时,为什么循环里反复调用就废了?
因为 time.After 每次都新建一个独立计时器,和“全局倒计时”完全不是一回事。
- 错误写法:
for range jobs { select { case → 每次循环都重置 1 秒,前面耗时全被丢弃 - 正确写法:
timeout := time.After(1*time.Second); for range jobs { select { case → 计时只启动一次,所有 <code>select共享同一超时信号 - 典型现象:某个 goroutine 已跑 7 秒,但因在最后一次
select的 1 秒窗口内返回,压根不触发超时
context.WithTimeout 怎么避免 goroutine 泄露?
很多人写了 context.WithTimeout,却忘了把 cancel() 传进子 goroutine,或者没在子任务里监听 ctx.Done(),结果超时后 goroutine 还在后台狂奔。
- 必须显式调用
cancel()(通常用defer cancel()),否则底层 timer 不会释放 - 子 goroutine 必须主动检查
ctx.Done(),不能只靠父 goroutine 等待 —— 否则超时后子任务还在跑,资源卡死 - 示例关键点:
go func(ctx context.Context) { ... select { case ,不是只把 ctx 传进去就完事
HTTP 请求超时,别只设 Timeout 一个字段
http.Client 的超时是分层的,只设 Timeout 会掩盖真实瓶颈,比如代理连不上、响应卡在半路。
立即学习“go语言免费学习笔记(深入)”;
-
Timeout是总超时(连接 + 写 + 读),但实际出问题常在某一层 - 生产环境建议拆开设:
Transport里配ProxyConnectTimeout、ConnTimeout、ReadTimeout、WriteTimeout - 例如代理慢导致卡住,
Timeout被耗尽,但你根本不知道是代理环节挂了
真正难的不是写对那一行 context.WithTimeout,而是想清楚“超时到底该由谁响应、谁清理、谁感知”。漏掉任何一个环节,超时就只是个摆设。


















