context.WithTimeout返回ctx和cancel函数,必须显式调用cancel以释放资源;HTTP请求需传入ctx并用NewRequestWithContext封装;阻塞操作应配合select+ctx.Done()监听取消信号。

context.WithTimeout 会自动触发 cancel 函数
调用 context.WithTimeout 不仅返回带超时的 ctx,还会返回一个 cancel 函数。这个 cancel 必须被显式调用(或让 defer 触发),否则底层定时器不会释放,可能造成 goroutine 泄漏。
- 超时触发时,
ctx.Err()返回context.DeadlineExceeded;手动调用cancel()则返回context.Canceled - 即使超时已发生,仍应调用
cancel()—— 它是幂等的,且能清理关联的 timer 和 channel - 常见错误:只用
ctx,却忘记 defercancel(),尤其在函数提前 return 时
HTTP client 请求必须传入 context.Context
Go 标准库的 http.Client 支持通过 Do 或 Get 等方法接收带取消信号的 ctx。不传 ctx 就无法响应上游取消或超时,请求会卡死直到 TCP 层超时(通常数分钟)。
-
http.DefaultClient不读取 context;必须用client.Do(req.WithContext(ctx)) - 推荐封装:用
http.NewRequestWithContext(ctx, ...)构造请求,再交给 client 发送 - 注意:DNS 解析、连接建立、TLS 握手、读响应体 —— 这些阶段都受
ctx控制,但具体中断时机取决于 Go 版本和底层 net.Conn 实现
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
// 处理超时或取消
}
return err
}select + ctx.Done() 是阻塞操作的标准退出模式
任何可能长期阻塞的操作(如 channel receive、time.Sleep、数据库查询)都应配合 ctx.Done() 使用 select,避免 goroutine 卡住无法响应取消。
-
ctx.Done()返回一个只读 channel,关闭即表示上下文结束 - 不要在 select 外单独检查
ctx.Err(),它可能为 nil;要等才能确认终止信号到达 - 如果操作本身支持 context(如
db.QueryRowContext),优先用它而非自己写 select
select {
case <-time.After(5 * time.Second):
return "done"
case <-ctx.Done():
return ctx.Err().Error() // 可能是 Canceled 或 DeadlineExceeded
}自定义操作中传递并检查 ctx.Err() 是关键习惯
你写的函数若涉及 I/O、重试、循环等待等,必须在每轮迭代开头检查 ctx.Err() != nil,否则即使父级已取消,子逻辑仍会继续执行。
- 不要只在函数入口检查一次;长时间运行的操作需“主动轮询”上下文状态
- 使用
errors.Is(err, context.Canceled)或errors.Is(err, context.DeadlineExceeded)判断错误类型,避免字符串比较 - 若调用第三方库(如 Redis client、gRPC stub),确认其方法是否接受 context;不支持的库需自行包装或降级处理
实际项目中最容易漏掉的是:在 defer 中调用 cancel() 后,又在函数中间因错误提前 return,导致后续逻辑没机会执行 defer —— 正确做法是把 cancel 放在最外层 defer,或用命名返回值+defer 统一处理。










