done()通道仅在取消、超时或截止到达时关闭;background/tod0的done()返回nil,select中被忽略;卡住主因是未触发关闭条件或误用nil channel,而非通道延迟。

Done 通道不是“一创建就可读”,它只在取消、超时或截止时间真正到达时才关闭;用 context.Background() 或 context.TODO() 的 Done() 会返回 nil,直接 select 就等于没写那个 case。
为什么 select 读 ctx.Done() 一直卡住?
这不是 channel 慢,而是你没触发关闭条件,或者拿了不该拿的 context:
- 用了
context.WithCancel()却忘了调用返回的cancel()函数 - 用了
context.WithTimeout(ctx, 5*time.Second),但还没到 5 秒,Done()自然不关 - 直接对
context.Background().Done()做select——它返回nil,Go 规范规定nilchannel 在select中永远不可读,该分支会被忽略(若无default)或导致阻塞(若有default但逻辑错判) - 在 goroutine 启动前就写了
done := ctx.Done(),而此时ctx是Background,done是nil,后续select永远等不到信号
ctx.Done() 关闭后,ctx.Err() 一定非 nil 吗?
是的,但反过来不成立:如果 ctx.Err() 是 nil,不能断定 Done() 没关(极短窗口下可能有偏差,实践中可忽略)。关键在于二者必须配对使用:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
select等是事件驱动退出的正确姿势,不是轮询 <code>ctx.Err() != nil——后者会空转 CPU - 退出后必须立刻查
ctx.Err()判断原因:errors.Is(ctx.Err(), context.Canceled)或errors.Is(ctx.Err(), context.DeadlineExceeded),别用== - 自定义 Context 实现时,
Done()关闭和err字段更新必须严格同步:先close(ch),再原子更新err;顺序颠倒会导致select收到关闭信号后ctx.Err()还是nil
HTTP handler 中 r.Context().Done() 为什么有时延迟明显?
不是 Done() 本身慢,而是触发依赖底层网络状态:
- 客户端主动断开(如浏览器关闭标签页),通常在收到 TCP FIN 或 RST 后 100ms 内触发,但受网络抖动、NAT 超时、代理缓冲影响
- 服务端主动超时(如
http.Server.ReadTimeout)由 Go runtime timer 控制,误差一般 - 中间件或封装层若提前缓存了
r.Context().Done()并复用,而该 context 来自Background,那整个链路根本不会响应请求中断 - 务必确认你监听的是 handler 入参的
r.Context(),而不是硬编码的context.Background()
最易被忽略的一点:Done channel 的关闭时机和 Err 返回值必须严格对齐。标准库 cancelCtx.cancel 函数里先 close channel,再原子更新 err 字段;手动实现时哪怕只差一行顺序,下游就可能拿到“已关闭但无原因”的上下文。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










