首选 context.withtimeout,因其返回带截止时间的 ctx 和 cancel 函数,支持父子传递、错误区分及标准库集成;http 用 withcontext、db 查询用 querycontext、自定义阻塞需每次监听 ctx.done(),超时时间须显式单位。

直接用 context.WithTimeout,别自己拼 time.After + select 做超时判断——它不传播取消、不联动子 goroutine、无法被主动终止,极易泄漏。
为什么 context.WithTimeout 是首选
它不是“加个定时器”,而是返回一个带截止时间的 ctx 和配套的 cancel() 函数,天然支持父子传递、错误类型区分(context.DeadlineExceeded vs context.Canceled)、以及与标准库深度集成。
- HTTP 请求必须用
http.NewRequestWithContext(ctx, ...),不能用http.Get—— 后者完全无视 context - 数据库查询必须用
db.QueryContext(ctx, ...),原生db.Query不响应取消 - 自定义阻塞逻辑(如轮询、sleep)必须在每次迭代前
select监听ctx.Done(),不能只检查一次ctx.Err() - 超时时间务必写成
time.Second * 3这种显式单位形式,避免3000这类无单位数字引发误读
select 中漏掉 ctx.Done() 就等于没超时
很多代码看着有 context,但实际只在函数开头检查一次 ctx.Err(),后面直接 或 <code>time.Sleep,结果超时信号根本进不来。
- channel 读写必须包裹在
select里:case val := + <code>case - 纯 CPU 计算任务无法被 context 中断,必须手动插入检查点,比如循环中每千次迭代
select { case - 若 channel 可能持续发送,
case 后要考虑是否要 drain channel,否则 sender goroutine 可能永久阻塞 - 不要在
select中混用无缓冲 channel 的发送操作(ch ),receiver 若已退出,该分支会永远卡住
cancel() 忘调就真会泄漏
context.WithTimeout 底层依赖 time.Timer,不调 cancel(),timer 就不会被 GC,长期运行的服务里累积多了会拖慢调度器。
- 必须
defer cancel(),哪怕函数提前return也要确保执行 - 子 goroutine 必须继承父
ctx,不能自己context.Background()重开,否则超时信号传不进去 - 高频重试场景别在循环里反复调
context.WithTimeout,应复用 timer 或用time.Ticker.Reset() -
http.Client超时不能只靠 context:DNS 解析、TCP 连接、TLS 握手都可能卡死,需配合Transport.DialContext和ResponseHeaderTimeout分层设限
最常被忽略的一点:context 超时是协作式的,不是抢占式的。它不会中断正在跑的 goroutine,只会发一个信号;你得在关键阻塞点主动监听并退出——否则再严谨的 timeout 设置也形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











