必须在每处可能阻塞的位置轮询 ctx.done(),因为 context.withtimeout 仅关闭 done channel,不主动中断 goroutine;http 客户端等已集成检查,但自定义循环、sleep、i/o 等需手动用 select + ctx.done() 判断。

不能只在函数开头检查 ctx.Err(),必须在每处可能阻塞的位置轮询 ctx.Done(),否则超时信号根本进不来。
为什么 context.WithTimeout 单独调用没用
很多人写完 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 就以为万事大吉,结果函数照常跑满 10 秒。这是因为 context.WithTimeout 只是发了个“通知”,不是“刹车”——它不会主动中断 goroutine,只是关闭 ctx.Done() 这个 channel。函数自己不监听、不响应,就等于没人听见警报。
-
time.AfterFunc同样只触发回调,完全无法停止正在执行的逻辑 - HTTP 客户端、数据库驱动等标准库组件内部已集成
ctx.Done()检查,所以传进去就能生效 - 但你自己写的循环、sleep、channel 收发、I/O 等操作,必须手动加
select判断 - 漏掉一处(比如在 for 循环里只检查一次),整个函数就失去超时控制能力
怎么写一个真正响应超时的函数
核心是:把所有可能耗时的操作,都包进 select,和 并列等待。不要用 <code>time.Sleep 直接阻塞,改用 time.After + select。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 错误写法:
time.Sleep(3 * time.Second)—— 完全无视 ctx - 正确写法:
select { case - 如果函数含多步(比如先查 DB、再调 HTTP、最后写日志),每步前都要加一次
select或等价判断 - 避免在
select里加default分支,否则变成忙等,CPU 拉满
db.WithContext(ctx) 是 GORM 超时控制唯一有效入口
GORM 不会从全局配置或默认 timeout 自动读取超时设置,DefaultTransactionTimeout 仅对没传 ctx 的调用兜底,实际无效。
- 错:
db.First(&u, 1)或db.Transaction(func(tx *gorm.DB) error { ... })—— 都没绑定 ctx - 对:
db.WithContext(ctx).First(&u, 1)或db.WithContext(ctx).Transaction(...) -
WithContext()返回新实例,原db不变;每次调用都要重新 wrap,不能复用 - 事务中嵌套子查询、Savepoint,必须复用同一个
ctx,不能在内部新建context.Background()
容易被忽略的资源清理点
超时发生后,ctx.Err() 是 context.DeadlineExceeded,但 goroutine 不会自动退出,你得立刻收手。
-
defer cancel()必须写,否则可能泄漏 goroutine(尤其在函数提前 return 时) - 超时后若还在用数据库事务,
tx.Commit()可能成功,但数据状态不可信,必须立刻tx.Rollback() - HTTP handler 里起的 goroutine 如果用了
c.Request.Context(),响应返回后 context 就被 cancel,异步任务应改用context.Background()+ 自定义 timeout - 收到
ctx.Done()后,除了 return,还应做必要清理:关文件、断连接、释放内存、记录日志
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










