Golang使用context取消并发任务

2次阅读

context.WithCancel 是最直接的取消方式,返回可取消 Context 和 cancel 函数,调用后者立即关闭 ctx.Done();必须配对调用 cancel()(推荐 defer),且需检查 ctx.Err() 区分取消原因。

Golang 使用 context 取消并发任务

context.WithCancel 是最直接的取消方式

当需要手动触发取消时,context.WithCancel 是首选。它返回一个可取消的 Context 和一个 cancel 函数,调用后者会立即关闭 ctx.Done() 的 channel。

常见错误是忘记调用 cancel() —— 这会导致 goroutine 泄漏,尤其在循环或高频创建 context 的场景中。

  • 每次调用 context.WithCancel 都必须配对调用 cancel(),建议用 defer cancel()(除非明确需延迟取消)
  • 不要把 cancel 传给不可信的第三方函数,它可能被意外调用
  • ctx.Done() 关闭后,再次调用 cancel() 是安全的,但无实际效果
ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 确保退出前清理 

go func() { select { case <-time.After(2 * time.Second): fmt.Println("task done") case <-ctx.Done(): fmt.Println("task canceled:", ctx.Err()) // context.Canceled } }()

time.Sleep(1 * time.Second) cancel() // 主动触发取消

select + ctx.Done() 必须配合 error 检查

仅监听 ctx.Done() 不足以判断是否真因取消退出——还需检查 ctx.Err(),否则无法区分是超时、取消,还是父 context 被关闭。

典型误用:在 select 中只读 就直接返回,不校验 ctx.Err(),导致日志或错误处理逻辑错判原因。

立即学习 go 语言免费学习笔记(深入)”;

  • ctx.Err()ctx.Done() 关闭后才返回非 nil 值;未关闭时为 nil
  • 如果父 context 已结束(如 WithDeadline 到期),子 context 的 Err() 可能是 context.DeadlineExceeded,不是 context.Canceled
  • HTTP handler 中常需将 ctx.Err() 映射为具体 HTTP 状态码 ,比如 context.Canceled → 499(Client Closed Request)

goroutine 中使用 context.Value 要格外谨慎

context.WithValue 适合传入请求范围的 ** 不可变元数据 **(如 trace ID、user ID),但绝不该用来传递业务参数或可变状态。

容易踩的坑是把 context.Value 当成“全局变量”替代参数传递,结果导致函数签名不透明、单元测试困难、静态分析失效。

  • 值类型必须是可比较的(如 stringint、自定义 struct),且 key 类型推荐用未导出的私有类型,避免冲突
  • 不要用 string 字面量当 key,例如 ctx = context.WithValue(ctx, "user_id", 123) —— 应定义 type ctxKey string; const userIDKey ctxKey = "user_id"
  • 高并发下 WithValue 有轻微性能开销(需拷贝 map),纯性能敏感路径应避免嵌套多层

http.Server 的 Shutdown 已自动集成 context 取消

Go 1.8+ 的 http.Server.Shutdown() 内部使用 context.Context 等待活跃连接关闭,无需手动包装。

常见误区是自己再套一层 WithTimeout 并监听 Done() 来“控制 Shutdown”,反而干扰默认行为,甚至导致连接被强制中断。

  • 直接调用 srv.Shutdown(ctx) 即可,传入带超时的 context 是合理做法,但别在外部额外 select 监听
  • Shutdown 不会取消正在执行的 handler,只阻止新请求;handler 内仍需自行检查 r.Context().Done()
  • 若 handler 使用了 long poll 或 streaming,务必在循环中持续检查 ctx.Err() == nil,否则无法响应 Shutdown

并发任务取消真正难的不是调用 cancel(),而是确保每个分支、每层封装、每次 I/O 都对 ctx.Done() 做响应 —— 尤其是第三方库是否尊重 context,往往得看文档或源码。

星耀云
版权声明:本站原创文章,由 星耀云 2026-01-06发表,共计511字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources