Go错误处理如何结合context使用_Go超时与取消处理

11次阅读

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

Go错误处理如何结合context使用_Go超时与取消处理

context.WithTimeout 会自动触发 cancel 函数

调用 context.WithTimeout 不仅返回带超时的 ctx,还会返回一个 cancel 函数。这个 cancel 必须被显式调用(或让 defer 触发),否则底层定时器不会释放,可能造成 goroutine 泄漏。

  • 超时触发时,ctx.Err() 返回 context.DeadlineExceeded;手动调用 cancel() 则返回 context.Canceled
  • 即使超时已发生,仍应调用 cancel() —— 它是幂等的,且能清理关联的 timer 和 channel
  • 常见错误:只用 ctx,却忘记 defer cancel(),尤其在函数提前 return 时

HTTP client 请求必须传入 context.Context

go 标准库http.Client 支持通过 DoGet 等方法接收带取消信号的 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 统一处理。

text=ZqhQzanResources