必须接收 ctx context.context 参数的函数是可能阻塞、发起 i/o 或启动需响应取消的 goroutine 的函数,如 http.client.do、sql.db.querycontext、grpc.clientconn.invoke 及其业务封装函数;纯内存计算函数无需 ctx。

哪些函数必须接收 ctx context.Context 参数?
只在函数可能阻塞、发起 I/O 或启动需响应取消的 goroutine 时才需要显式接收 ctx。比如:http.Client.Do、sql.DB.QueryContext、grpc.ClientConn.Invoke,或者封装了这些调用的业务函数(如 fetchUser(ctx, id))。纯内存计算函数(如 calculateHash(data)、parseJSON(b))传 ctx 是冗余的,也破坏职责边界。
常见错误是硬编码 context.Background() 在函数内部——这会让调用方彻底失去超时、取消和元数据注入能力。HTTP handler 中尤其要注意:必须用 r.Context(),而不是新建 context.Background()。
如何用 context.WithTimeout 包裹任务?
context.WithTimeout 是限制执行时间最直接的方式,但它不是“自动中断”,而是提供协作式退出信号。任务必须主动监听 ctx.Done() 并在关键点退出,否则超时无效。
- 必须配对使用返回的
cancel函数,哪怕任务提前完成也要defer cancel();漏掉会导致 timer 泄露 - 超时从
WithTimeout调用那一刻开始计,不是从后续操作开始 - 对标准库组件(如
http.Client.Do、db.QueryContext),直接传入ctx即可,它们会在超时后自动释放连接和资源 - CPU 密集型循环中,必须手动插入
select { case 判断,否则 context 完全不生效
为什么有时 context.WithTimeout 看似不生效?
根本原因在于底层系统调用(如 DNS 解析、TCP 建连、TLS 握手)在旧版 Go 或特定平台(如 Windows)上无法响应 ctx.Done()。它只保证 http.Do 返回后能及时退出,不保证“立刻断开正在建连的 socket”。
更可靠的做法是组合使用:
- 设置
http.Client.Timeout控制整个请求生命周期 - 配置
http.Transport.DialContext和http.Transport.TLSHandshakeTimeout分阶段控制建连与握手 - Go 1.19+ 可启用
GODEBUG=netdns=cgo让 DNS 解析响应 cancel(但依赖 cgo)
子 context 的 cancel 信号会自动传播到孙子级吗?
不会。调用 cancel() 后,取消信号只同步通知**直接派生的子 context**(包括 WithCancel、WithTimeout、WithValue 创建的),不会自动穿透到“孙子级”以下。如果孙子级要响应取消,必须显式监听父级 ctx.Done(),不能依赖隐式继承。
另一个容易被忽略的点是:不要对同一个 ctx 多次调用 WithTimeout —— 子 context 的 deadline 是叠加的,容易误判超时逻辑。真正需要多层控制时,应从原始 parent 派生,而非链式派生。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











