ctx.done() 返回的 channel 仅在 context 被取消、超时或截止时间到达时关闭,由 runtime 自动管理;background 和 todo 的 done() 永不关闭;重复调用 ctx.done() 安全,但缓存 nil 或提前读取会导致 select 阻塞。

ctx.Done() 返回的 channel 什么时候关闭
它只在 Context 被取消(cancel)、超时(timeout)或截止时间(deadline)到达时关闭。不是“一创建就可读”,也不是“手动调用才关”——背后是 runtime 对 context.cancelCtx 或 context.timerCtx 状态的自动管理。
常见错误现象:select 里读 ctx.Done() 却一直阻塞,以为 Context “没生效”;其实是没触发取消条件,比如忘了调用 cancel() 函数,或 WithTimeout 的计时器还没到时间。
- 使用场景:HTTP handler 中监听请求中断、数据库查询中响应 context 取消、goroutine 清理前等待 Done
-
context.Background()和context.TODO()的Done()永远不关闭(返回 nil channel),别拿它们做取消判断 - 自定义 Context(如实现
Context接口)时,若没正确管理Done()channel 的生命周期,会导致 goroutine 泄漏
为什么不能多次从 ctx.Done() 读取并缓存 channel
每次调用 ctx.Done() 都返回同一个底层 channel,但它的关闭是单向不可逆的。问题不在“重复调用”,而在于有人误以为可以“缓存变量”后反复 select ——这本身没问题;真正踩坑的是:缓存了 nil(比如从 Background 来的),或在 goroutine 启动前就提前读了一次,导致后续 select 永远收不到通知。
- 错误写法:
done := ctx.Done(); select { case 在 cancel 发生前就执行了,而 done 此时还没关闭,之后也没再检查 - 正确姿势:
select必须在关键路径上原地调用ctx.Done(),或确保缓存时机安全(如刚拿到子 context 后立刻保存) - 性能影响:无。函数返回的是指针引用,不是新建 channel
ctx.Err() 和
是通知“该停了”,<code>ctx.Err() 是告诉你“为什么停”。两者必须配对使用:仅靠 Done() 无法区分是用户主动 cancel 还是超时,而只查 Err() 不读 Done() 会错过实时性——因为 Err() 是同步函数,不阻塞,但无法替代事件通知。
立即学习“go语言免费学习笔记(深入)”;
- 典型误用:在 for 循环里只轮询
ctx.Err() != nil,导致 CPU 空转;正确做法是select等ctx.Done(),退出后再用ctx.Err()判断原因 -
ctx.Err()返回值是error类型:context.Canceled或context.DeadlineExceeded,注意用errors.Is()比较,别用 == - 如果 context 尚未取消,
ctx.Err()返回 nil,此时仍会阻塞
WithCancel/WithTimeout 创建的子 context 如何真正释放资源
调用 cancel() 函数只是标记状态并关闭 Done() channel,不会自动回收 goroutine 或关闭网络连接。真正的清理必须由使用者在收到 Done() 信号后显式完成。
- 容易忽略的点:cancel 函数本身不阻塞,也不等子 goroutine 结束;你得自己
sync.WaitGroup或用select配合donechannel 等待它们退出 - 数据库驱动(如
database/sql)支持传入 context,但仅限于控制查询发起阶段;执行中的 long-running query 是否中断,取决于驱动实现和数据库协议(例如 PostgreSQL 支持 cancellation,MySQL 5.7+ 需要配置) - HTTP client 默认把 context 透传给 transport,但底层 TCP 连接不会立刻断开;超时后请求可能仍在服务端处理,只是 client 不再等待响应










