Go 语言 Context:并发控制的标准答案
Go 语言的 context 包是并发编程的”任务指挥官”。它管理 goroutine 的生命周期、传播取消信号、传递截止时间。
一、为什么需要 Context?
没有 Context 的痛苦
| 问题 | 后果 |
|---|---|
| Goroutine 失控 | 资源泄漏、后台幽灵任务 |
| 停止信号难传递 | 需要自己设计 channel 机制 |
| 请求数据传递混乱 | 函数签名冗长、耦合度高 |
Context 的核心价值
生活比喻:Context 就像派对策划人
– 知道派对何时开始、何时结束(Deadline/Timeout)
– 能提前通知所有人”派对取消”(Cancellation)
– 携带特殊要求(如着装主题)(Value)
技术价值:
– 统一的生命周期管理
– 标准化的取消信号传播
– 优雅的超时控制
– 请求范围数据传递
二、Context 接口:四个核心方法
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
| 方法 | 作用 |
|---|---|
Deadline() |
查询是否设置了截止时间 |
Done() |
返回只读 channel,关闭时表示取消 |
Err() |
获取取消原因(Canceled/DeadlineExceeded) |
Value() |
检索键值对数据 |
关键理解:Context 是一种”协作契约”。函数接受 Context 意味着承诺”倾听并遵守”取消信号。
三、创建根 Context
context.Background()
特性:永不取消、无值、无截止时间
使用场景:
– main() 函数启动服务
– 单元测试
– 服务器处理入站请求的顶层
context.TODO()
特性:功能同 Background,但语义是”临时方案”
使用场景:
– 不确定该用哪个 Context 时
– 计划后续重构时
区别:Background 是”正式起点”,TODO 是”待办标记”
// 正确
rootCtx := context.Background()
// 错误!永远不要传递 nil
// var ctx context.Context = nil
四、派生子 Context
4.1 可取消:WithCancel
ctx, cancel := context.WithCancel(parentCtx)
defer cancel() // 必须调用,释放资源
工作原理:
– 调用 cancel() 函数关闭 ctx.Done() channel
– 取消信号自动传播到所有子 Context
– 必须配合 defer cancel() 使用
4.2 超时控制:WithTimeout
timeoutCtx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
defer cancel()
特点:相对时间,2 秒后自动取消
本质:WithTimeout 是 WithDeadline 的便捷包装
4.3 截止时间:WithDeadline
deadlineCtx, cancel := context.WithDeadline(parentCtx, time.Date(2025, 12, 31, 23, 59, 59, time.UTC))
defer cancel()
特点:绝对时间点,到达即取消
4.4 传递数据:WithValue
type userIDKey string
ctx := context.WithValue(parentCtx, userIDKey("userID"), 123)
userID := ctx.Value(userIDKey).(int)
最佳实践:
– 定义自定义键类型(避免冲突)
– 只传递请求范围数据
– 不要传递普通函数参数
五、完整示例
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context, id int) {
for {
select {
case <-time.After(1 * time.Second):
fmt.Printf("Worker %d: working...\n", id)
case <-ctx.Done():
fmt.Printf("Worker %d: stopped, reason: %v\n", id, ctx.Err())
return
}
}
}
func main() {
rootCtx := context.Background()
// 3 秒超时
timeoutCtx, cancel := context.WithTimeout(rootCtx, 3*time.Second)
defer cancel()
// 启动两个 worker
go worker(timeoutCtx, 1)
go worker(timeoutCtx, 2)
time.Sleep(5 * time.Second)
fmt.Println("Main: exiting")
}
输出:
Worker 1: working...
Worker 2: working...
Worker 1: working...
Worker 2: working...
Worker 1: stopped, reason: context deadline exceeded
Worker 2: stopped, reason: context deadline exceeded
Main: exiting
六、常见陷阱
| 陷阱 | 后果 | 解决 |
|---|---|---|
| 忘记调用 cancel() | 资源泄漏 | 始终使用 defer cancel() |
| 传递 nil Context | 可能 panic | 用 context.Background() |
| Value 传递函数参数 | 代码不清晰 | 用正常的函数参数 |
| 子 Context 不检查 Done() | 无法响应取消 | 在循环中 select 监听 |
七、小结
Context 的本质:
– ✅ 并发控制的标准方案
– ✅ 生命周期管理的优雅工具
– ✅ Go 协作式并发模型的核心
核心原则:
1. 永远不要传递 nil Context
2. 始终调用 defer cancel()
3. 在 goroutine 中检查 ctx.Done()
4. Value 只传请求范围数据
何时使用:
– HTTP 请求处理
– 数据库/网络调用
– 任何需要控制生命周期的操作
参考链接
– Go 官方博客:https://go.dev/blog/context
– Context 包文档:https://pkg.go.dev/context
– 原文来源:docs.80aj.com






AI周刊:大模型、智能体与产业动态追踪
程序员数学扫盲课
冲浪推荐:AI工具与技术精选导航
Claude Code 全体系指南:AI 编程智能体实战
最新评论
开源的AI对话监控面板很实用,正好团队在找这类工具。准备试用一下。
折叠屏市场确实在升温,不过售罄也可能是备货策略。期待看到实际销量数据。
从磁盘I/O角度解释B树的设计动机,这个切入点很好。终于理解为什么数据库不用二叉树了。
IT术语转换确实是个痛点,之前用搜狗总是把技术词汇转成奇怪的词。智谱这个方向值得期待。
这个工具结合LLM和搜索API的思路很有意思,正好解决了我在做知识管理时遇到的问题。请问有没有部署文档?
这个漏洞确实严重,我们团队上周刚遇到类似问题。建议补充一下如何检测现有项目是否受影响的方法。
从简单规则涌现复杂性这个思路很有意思,让我想起元胞自动机。不过数字物理学在学术界争议还挺大的。
我也遇到了指令跟随变差的问题,特别是多轮对话时容易跑偏。不知道是模型退化还是负载优化导致的。