Go并发编程中的context使用实践
在 Go 服务端开发中,context 经常和 HTTP 请求、数据库查询、RPC 调用、后台 goroutine 一起出现。很多初学者知道它可以“传递上下文”,但在实际项目里更重要的作用是控制超时、取消任务和传递请求级元数据。
如果没有统一的取消机制,一个请求已经断开,后面的数据库查询、第三方接口调用和 goroutine 仍然可能继续运行。并发量上来之后,这类问题会表现为连接池耗尽、CPU 空转、日志堆积和接口尾延迟变大。
context 解决什么问题
context.Context 主要用于跨 API 边界传递三个信息:
- 取消信号:上游不再需要结果时,下游应尽快停止。
- 截止时间:任务必须在某个时间点前结束。
- 请求级数据:例如 trace id、用户 id、语言环境等轻量信息。
它不是全局变量,也不是业务参数容器。对于订单号、分页参数、查询条件这类业务数据,应该继续通过普通函数参数传递。
基础用法
最常见的是从 context.Background() 派生一个带超时的上下文:
1 | package main |
这段代码中任务本身需要 3 秒,但上下文 2 秒后超时,因此 slowTask 会返回 context deadline exceeded。真正的项目代码不会使用 time.After 模拟耗时,而是把 ctx 继续传给数据库、HTTP 客户端或其他内部函数。
在 HTTP 服务中传递 context
Go 标准库的 http.Request 自带 context。一次请求进来后,应优先使用 r.Context() 作为根上下文,而不是重新创建 context.Background()。
1 | func handler(w http.ResponseWriter, r *http.Request) { |
当客户端断开连接、网关超时或服务端主动取消请求时,r.Context() 会被取消。只要下游函数正确监听 ctx.Done() 或把 ctx 传给支持 context 的库,任务就能及时退出。
数据库查询中的 context
标准库 database/sql 提供了 QueryContext、ExecContext 和 BeginTx 等方法。使用这些方法可以让数据库查询响应请求超时。
1 | func queryUser(ctx context.Context, id string) (*User, error) { |
这里在请求上下文之上再加了数据库查询的局部超时。这样做的好处是:即使整个 HTTP 请求允许 3 秒,单次数据库查询也不会无限等待。
goroutine 中的退出控制
启动 goroutine 时,必须考虑它什么时候结束。下面是一个常见的后台轮询任务:
1 | func startWorker(ctx context.Context) { |
如果 ctx 被取消,worker 会结束循环并退出。这个模式适合服务关闭、任务取消、测试清理等场景。
如果 goroutine 内部还会调用耗时函数,也要继续向下传递 ctx,不要只在最外层检查一次。
使用 Value 的边界
context.WithValue 可以传递请求级数据,但要克制使用。它适合 trace id、request id、认证后的用户摘要等横切关注点,不适合传递业务参数。
推荐定义私有 key 类型,避免和其他包冲突:
1 | type contextKey string |
不要使用普通字符串作为 key,因为不同包可能使用相同字符串,导致值被覆盖或读错。
常见错误
第一个错误是忘记调用 cancel。context.WithTimeout 和 context.WithCancel 返回的取消函数应该在合适位置调用,通常使用 defer cancel()。这样可以释放计时器和相关资源。
第二个错误是在函数内部使用 context.Background()。除非是在程序入口、测试入口或确实创建根任务,否则内部函数应该接收上游传入的 ctx。
第三个错误是把 context 放进结构体长期保存。context 应该作为函数调用链的一部分显式传递,通常放在函数第一个参数。
第四个错误是只创建超时 context,但下游代码完全不使用它。只有当耗时逻辑监听 ctx.Done(),或者调用支持 context 的 API,取消信号才有意义。
小结
context 的核心价值不是“共享变量”,而是让一组调用在同一个请求生命周期内协同退出。实际项目中可以遵循几个简单规则:函数第一个参数传 ctx,不要在中间层创建新的根 context,带超时的 context 记得 cancel,业务参数不要塞进 Value。
当这些约定形成习惯后,服务在超时控制、优雅关闭和故障隔离方面都会稳定很多。







