必须在队列消费入口用context.WithTimeout统一控制整个任务链超时,每个任务启动时创建独立ctx并在所有阻塞点监听ctx.Done(),HTTP和DB操作需用WithContext版本,延迟队列应复用Timer避免泄漏。

context.WithTimeout 必须用在队列消费入口,不能只包单个 I/O
队列超时不是给某次 db.Query 或 http.Do 加个 timeout 就完事。整个任务链(接收 → 解析 → 查库 → 调第三方 → 写结果)必须从最外层统一计时,否则超时信号永远进不到深层逻辑里。
- 错误写法:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)只在 main 启动时调一次,然后传给 worker,worker 内部却用time.Sleep或轮询不检查ctx.Done() - 正确做法:每个任务启动时都生成独立
ctx, cancel := context.WithTimeout(parentCtx, 3*time.Second),并在所有阻塞点监听 —— 包括select、time.Sleep、channel receive前 - 别把
context.Background()当默认值塞进 worker,子 goroutine 必须继承父 ctx,否则取消信号断链 - HTTP 请求必须用
http.NewRequestWithContext(ctx, ...),http.Get完全无视 context - 数据库查询必须用
db.QueryContext(ctx, ...),原生db.Query不响应取消
延迟队列别滥用 time.AfterFunc,大量动态任务要用最小堆 + 单 Timer 复用
如果你的“延迟队列”只是偶尔发几条 5 秒后执行的通知,time.AfterFunc 确实够用;但一旦任务量上到几百/千级、且到期时间各不相同,每任务一个 time.Timer 会快速拖垮调度器。
-
time.AfterFunc返回的是*time.Timer,调用Stop()才能回收资源;漏掉就真泄漏 - 高频创建/销毁
time.Timer会导致 GC 压力上升,runtime timer heap 持续膨胀 - 正确方案:用
container/heap维护按DueAt排序的最小堆,只起一个常驻 goroutine,每次heap.Pop到期任务并执行,再用timer.Reset()指向下一个最近到期时间 - 堆元素结构体必须实现
Less(i, j int) bool,比较字段是DueAt,不是 ID 或其他无关字段 - 别在堆里存指向外部状态的指针,避免竞态;任务函数尽量无状态,或用
sync.Map配合唯一 ID 查状态
select 中漏 default 或漏 ctx.Done(),等于没加超时
很多代码看着用了 context,实际只在函数开头检查一次 ctx.Err(),后面直接 for { time.Sleep(100 * time.Millisecond) } —— 这种写法超时完全无效,因为 time.Sleep 不响应 cancel。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 轮询类逻辑(如查任务状态、等下游回调)必须写成
select { case ,否则会卡死在最后一次 <code>time.After上 - 非阻塞写入队列时,
select { case tasksCh 是标准姿势;没 <code>default就会阻塞生产者 - channel 读写操作必须包裹在
select里,不能裸写val := ,否则无法被超时中断 - 如果 channel 可能持续发送而 receiver 已退出,记得 drain:循环
select { case ,否则 sender goroutine 永远卡住
cancel() 忘 defer 就真泄漏,Timer 不回收会拖慢调度器
context.WithTimeout 底层依赖 time.Timer,而 time.Timer 不调 Stop() 或不被 cancel() 触发,就不会被 GC。长期运行的服务里累积几千个 idle timer,会明显拖慢 Go runtime 的调度性能。
立即学习“go语言免费学习笔记(深入)”;
- 必须
defer cancel(),哪怕函数提前return也要确保执行 - 子 goroutine 里如果自己调了
context.WithTimeout,也得自己defer cancel(),不能指望父级代劳 - 高频重试场景别在 for 循环里反复调
context.WithTimeout,应复用time.Ticker或手动管理time.Timer.Reset() - http.Client 超时不能只靠 context:DNS 解析、TCP 连接、TLS 握手都可能卡死,需配合
Transport.DialContext和ResponseHeaderTimeout分层设限 - 最关键的提醒:
context 超时是协作式的,不是抢占式的—— 它不会中断 goroutine,只发信号;你得在关键阻塞点主动监听并退出,否则再严谨的 timeout 也形同虚设

















