go context 是父子协程间的信号广播树,非数据管道;withcancel 等派生新 ctx 建立单向监听关系,父 cancel 通过 close channel 级联关闭子 done;withvalue 是链式只读查找,非共享内存;超时本质是 timer 触发 cancel。

Go 的 Context 不是“传递”数据的管道,而是父子协程间**信号广播树**——你不是把 ctx “传过去”,而是在派生新 ctx 时建立取消/超时的监听关系。
为什么 context.WithCancel 派生后父 cancel 就能影响子
因为 context.WithCancel 返回的子 Context 内部持有一个指向父 Context 的引用,并在父 Done() 关闭时自动关闭自己的 Done() channel。这不是轮询,而是通过 close() 的 channel 关闭语义实现的级联通知。
- 所有派生函数(
WithCancel、WithTimeout、WithValue)都返回新 struct 实例,不修改原 ctx - 子 ctx 的
Done()在内部监听父Done(),一旦父关闭,子立刻关闭(同步、无延迟) - 多个子 ctx 可同时监听同一个父,cancel() 会同时通知全部
- 没有“反向传播”:子 cancel 不会影响父,只支持单向树形广播
ctx.Value() 看似传递值,实际是只读快照
context.WithValue 并不把值“塞进”父 ctx,而是创建一个新 ctx,其 Value() 方法在查 key 时先查自己存储的键值对,查不到就递归调用父 Value()。它本质是链式查找,不是共享内存。
- key 类型推荐用私有 struct(如
type userKey struct{}),避免字符串 key 冲突 - value 必须是线程安全的;如果存 map/slice,需自行加锁或用不可变副本
- 不能用于传业务参数——函数参数更清晰、可测试、不隐式
- 高频调用中
Value()是 O(n) 链表遍历,深度过深(>5 层)会有可观开销
超时与截止时间本质都是基于 timer 的自动 cancel
context.WithTimeout 和 context.WithDeadline 底层都靠 time.Timer 驱动。一旦触发,它们做的唯一一件事就是调用内部封装的 cancel() 函数——和你手动调 cancel() 完全等价。
-
WithTimeout(ctx, d)等价于WithDeadline(ctx, time.Now().Add(d)) - Timer 启动后,若父 ctx 先被 cancel,该 timer 会立即
Stop(),防止 goroutine 泄漏 - 不要重复
defercancel():多次调用 cancel 是安全的,但没意义;漏调则 timer 继续运行,ctx 泄漏 - 在 HTTP handler 中,
r.Context()已带超时,通常不应再套一层WithTimeout,除非明确要覆盖
真正容易被忽略的是:Context 的取消信号到达后,goroutine 是否退出,完全取决于你有没有检查 或调用 <code>ctx.Err()。Context 不会强制杀死 goroutine,它只提供通知机制——漏检 Done(),就等于没接收到信号。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











