函数签名必须以ctx context.Context为首个参数,否则无法实现超时控制;所有阻塞操作须在select中监听ctx.Done(),漏掉任一阻塞点都会导致goroutine泄漏。

函数签名必须接收ctx context.Context参数
不加这个参数,超时控制就无从谈起。Go标准库(如http.Client.Do、database/sql.Rows.Next)和主流驱动都要求第一参数是ctx,这是信号传入的唯一入口。如果函数自己实现阻塞逻辑(比如轮询、sleep、channel等待),也必须显式接收ctx,否则无法感知取消。
常见错误:把ctx藏在结构体字段里,或只在函数开头检查ctx.Err()就返回——这只能防止启动,不能中断已运行的逻辑。
- 正确姿势:所有可能阻塞的函数,签名以
func(ctx context.Context, ...)开头 - 错误姿势:
func(doWork())+ 内部硬编码time.Sleep(5 * time.Second) - 更隐蔽的错:
func(work(ctx context.Context))但内部没用ctx.Done()做select
每处阻塞点都要select监听ctx.Done()
超时不是“设了就生效”,而是靠函数主动在每个可能卡住的地方轮询ctx.Done()。哪怕只漏掉一个time.Sleep或一次chan<-发送,goroutine就会继续跑下去,形成泄漏。
示例中常见陷阱:
立即学习“go语言免费学习笔记(深入)”;
-
time.After(3 * time.Second)不响应cancel,必须改用select { case - 循环中调用
db.QueryRow(...),但没用db.QueryRowContext(ctx, ...) - 读取
io.ReadFull(r, buf)前没检查ctx.Err(),导致TCP连接卡死在read系统调用
关键原则:只要操作可能超过毫秒级,就该在它前面加一层select或改用支持ctx的上下文版本API。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
cancel()必须显式调用,不能只靠defer
context.WithTimeout返回的cancel函数,本质是释放底层定时器和关闭Done()通道。如果函数因校验失败提前return、或发生panic未被recover,defer cancel()根本不会执行,导致定时器持续驻留、内存泄漏。
推荐写法是“显式+defer双保险”:
- 在所有正常出口(包括
if err != nil分支)前手动调用cancel() - 再补一句
defer cancel()兜底,覆盖panic等异常路径 - 特别注意:HTTP handler里若派生子
ctx用于DB查询,子cancel()也要同样处理,不能只在handler末尾调一次
别忽略ctx.Err()的返回值和错误类型
超时后ctx.Done()关闭,ctx.Err()会返回context.DeadlineExceeded或context.Canceled。很多代码只检查err != nil就直接返回,却没区分是业务错误还是超时信号,导致上游无法做重试或降级决策。
实际处理建议:
- 对
context.DeadlineExceeded,通常应记录为慢请求,不重试,可能触发熔断 - 对
context.Canceled,大概率是客户端断连,可忽略或记为“主动取消” - 不要用
errors.Is(err, context.DeadlineExceeded)以外的方式判断——因为ctx.Err()可能被包装,==比较会失效
最易被忽略的一点:超时控制的有效性,完全取决于你是否在**每一个**阻塞位置做了响应。没有银弹,只有逐行检查。

















