context.withcancel不能自动终止goroutine,仅通过关闭ctx.done()通道发送取消信号;子协程必须主动监听该信号并自行退出,否则无法响应取消、释放资源或中断阻塞操作。

Go 的 context.Context 本身不管理协程生命周期,它只传递取消信号和值;真正终止协程靠的是接收方主动检查 ctx.Done() 并退出 —— 这点不理解,协程就永远卡着。
为什么 context.WithCancel 不能自动停掉正在跑的 goroutine
很多人以为调用 cancel() 就能“杀死”协程,其实不是。context 只是发个通知(往 ctx.Done() channel 发一个关闭信号),协程必须自己监听并响应。没监听,就等于没听见。
-
ctx.Done()是一个只读 channel,关闭时会立即可读,可用select非阻塞检测 - 协程内部若在做阻塞 I/O(如
http.Get)、无休止循环或没检查ctx.Err(),就不会退出 - 标准库中部分函数(如
http.Client.Do、time.AfterFunc)原生支持context,但自定义逻辑必须手动集成
怎样让一个 HTTP 请求协程响应 context 取消
直接传 ctx 给 http.NewRequestWithContext,再用支持 context 的 client 发起请求 —— 这是最简且安全的做法。别自己封装 select 去等 http.Do 和 ctx.Done(),容易漏错误处理或造成资源泄漏。
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com", nil)
resp, err := http.DefaultClient.Do(req) // 自动响应 ctx 取消
if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
// 处理取消或超时
}
return
}
- 务必用
http.NewRequestWithContext,而不是先建http.Request再赋值ctx(后者无效) -
http.DefaultClient默认支持 context;若用自定义http.Client,确保没禁用 timeout 或覆盖 Transport 行为 - 不要在
resp.Body读取前就 return,否则连接可能复用失败或泄漏
如何用 context.WithTimeout 控制数据库查询时间
对 database/sql 操作,context 要传给具体执行方法(如 QueryContext、ExecContext),不是传给 *sql.DB 构造时。驱动层(如 lib/pq 或 mysql)负责将信号转为底层 cancel 操作。
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
<p>rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE active = $1", true)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Println("query timed out")
}
return
}
defer rows.Close()
</p>
- 超时后,PostgreSQL 驱动会发
pg_cancel_backend,MySQL 驱动会调KILL QUERY,但前提是驱动版本够新(如pgx/v5、go-sql-driver/mysql v1.7+) - 如果用的是老驱动或自定义封装,可能只做到“客户端放弃等待”,而服务端查询仍在运行
- 避免把同一个
ctx重复传给多个并发查询——应为每个操作新建子 context,防止一个失败影响其他
用 errgroup.Group 协同多个带 context 的协程
当要并发发起几个可能互相影响的请求(比如同时查用户、订单、权限),又希望任一失败或超时就整体退出,errgroup.Group 是比手写 sync.WaitGroup + select 更可靠的选择。它自动传播 cancel,并聚合第一个 error。
g, ctx := errgroup.WithContext(context.Background())
g.Go(func() error {
return fetchUser(ctx, userID)
})
g.Go(func() error {
return fetchOrders(ctx, userID)
})
g.Go(func() error {
return fetchPermissions(ctx, userID)
})
if err := g.Wait(); err != nil {
// 任一子任务返回非-nil error 或 ctx 被 cancel,这里就会退出
}
-
errgroup.WithContext创建的ctx会在任意Go函数返回 error 或 panic 时自动 cancel - 所有子任务必须显式使用该
ctx(比如传给 HTTP 请求或 DB 查询),否则 cancel 不生效 - 注意:如果某个子任务启动后立刻 panic,
errgroup会捕获并 cancel 其他任务;但如果它卡在阻塞系统调用里且没响应ctx,依然会拖慢整体结束
最常被忽略的一点:context 的 value 传递不是用来传业务参数的,而是传请求范围的元数据(如 traceID、userIP)。别把 struct 或大对象塞进 ctx.WithValue,既影响性能,也破坏 context 的语义边界。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











