context.WithTimeout通过ctx.Done()主动取消goroutine,需确保HTTP、DB、channel等操作响应取消信号;http.Client需分层设超时,select必须包裹channel操作,超时后须手动清理资源。

用 context.WithTimeout 控制 goroutine 生命周期
超时不是靠 time.Sleep 等出来的,而是通过 context.Context 主动取消 goroutine。核心是让所有可能阻塞的操作(如 HTTP 请求、数据库查询、channel 读写)能响应 ctx.Done() 信号。
常见错误是只对主逻辑加超时,但子调用(比如第三方库的 http.Client.Do)没传入 context,导致超时失效。
-
context.WithTimeout返回ctx和cancel,必须在函数退出前调用cancel(),否则可能泄漏 goroutine - HTTP 客户端需显式使用
http.NewRequestWithContext(ctx, ...),默认的http.Get不支持 context - 自定义 channel 操作要配合
select+ctx.Done(),不能直接
HTTP 请求超时必须分层设置
Go 的 http.Client 有三个关键超时字段,它们作用不同,漏设任一都可能让整体超时不生效:
-
Timeout:整个请求从开始到响应 body 读完的总时间(含 DNS、连接、TLS、发送、等待首字节、接收 body) -
Transport.Timeout:已弃用,不要用 -
Transport.DialContext+Transport.TLSClientConfig:需单独控制连接建立和 TLS 握手时间 - 推荐组合:
Timeout设为业务总超时,再用Transport中的DialContext和ResponseHeaderTimeout做细粒度约束
示例中若只设 client.Timeout = 5 * time.Second,但 DNS 解析卡住 10 秒,请求仍会等满 10 秒才失败——因为 DNS 超时由底层 net.Resolver 控制,默认无限制。
立即学习“go语言免费学习笔记(深入)”;
select 配合 ctx.Done() 处理 channel 超时
goroutine 间通信常依赖 channel,但 是阻塞操作,必须用 select 包裹才能响应超时。
错误写法:val := —— 完全无法中断,context 超时无效。
正确模式:
select {
case val := <-ch:
// 正常收到数据
case <-ctx.Done():
// 超时或被取消,此时应 return 或 cleanup
return ctx.Err()
}- 注意
ctx.Done()是只读 channel,多次不会 panic,但首次关闭后始终立即返回 - 如果 channel 可能发多个值,别在
case 后直接 return,需考虑是否要 drain channel 防止 sender goroutine 卡住 - 避免在 select 中混用无缓冲 channel 的发送操作(
ch ),若 receiver 已退出,该 case 会永远阻塞
慎用 time.AfterFunc 替代 context 超时
time.AfterFunc 看似简单,但它无法取消已启动的函数,也不能传递取消信号给下游操作,仅适合“到点就执行一次”的场景(如打日志、发指标)。
- 它不管理 goroutine 生命周期,超时后原任务仍在运行,可能造成资源泄漏或重复写入
- 无法与
sync.WaitGroup或其他 context-aware 组件协同 - 真正需要中断的任务(如正在上传大文件、处理长 SQL)必须用
context驱动,而非靠定时器“事后补救”
最易忽略的一点:超时后的 cleanup 往往比超时触发更难写。比如一个 goroutine 正在写文件,超时后不仅要关掉它,还得确保临时文件被清理、锁被释放、数据库事务回滚——这些都得在 分支里手动做,没有自动机制。










