下面从 设计动机 → 核心接口 → 内部结构 → 常见用法 → 典型坑点 五个层次,系统解读 Go 语言中的 context。
一、为什么需要 context
在 Go 中,并发是常态,但带来了几个问题:
-
goroutine 无法被强制杀死
-
函数调用链中难以统一取消
-
超时、截止时间难以传递
-
跨 API / RPC / 中间件传递请求元数据
👉 context 的本质:
一种 在 API 边界和 goroutine 之间传递取消信号、超时和请求作用域数据的标准方式
二、Context 接口定义
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
各方法含义
| 方法 | 作用 |
|---|---|
Deadline() |
返回 context 的截止时间 |
Done() |
返回一个只读 channel,context 被取消时关闭 |
Err() |
返回取消原因 |
Value() |
获取上下文中的请求作用域数据 |
三、Context 的四种创建方式
1️⃣ context.Background()
ctx := context.Background()
-
根 context
-
永不取消
-
用于 main / init / 测试
2️⃣ context.TODO()
ctx := context.TODO()
-
语义同 Background
-
表示"暂时不确定用什么 context"
3️⃣ 可取消 Context
ctx, cancel := context.WithCancel(parent)
defer cancel()
✅ 特点:
-
手动取消
-
级联取消所有派生 context
4️⃣ 带超时的 Context
ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel()
ctx, cancel := context.WithDeadline(parent, time.Now().Add(2*time.Second))
四、Context 的底层结构(简化)
cancelCtx(核心)
type cancelCtx struct {
Context
mu sync.Mutex
done atomic.Value // chan struct{}
children map[canceler]struct{}
err error
}
关键点
-
每个
WithCancel创建一个 取消节点 -
取消是 树状传播
-
Done()返回的 channel 只关闭一次
五、标准使用模式
✅ 正确示例:请求链路取消
func worker(ctx context.Context) {
select {
case <-time.After(5 * time.Second):
fmt.Println("done")
case <-ctx.Done():
fmt.Println("cancelled:", ctx.Err())
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go worker(ctx)
time.Sleep(3 * time.Second)
}
✅ HTTP 服务中使用
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(3 * time.Second):
w.Write([]byte("ok"))
case <-ctx.Done():
http.Error(w, "client disconnected", 499)
}
}
六、Context 传值(WithValue)
ctx := context.WithValue(context.Background(), "userID", 123)
userID := ctx.Value("userID").(int)
⚠️ 使用原则
-
只用于请求作用域数据
-
不要用做参数替代
-
Key 应使用自定义类型,避免冲突
✅ 推荐写法:
type ctxKey string
const userKey ctxKey = "user"
ctx := context.WithValue(ctx, userKey, "alice")
七、Context 使用规范(非常重要)
✅ 必须遵守
-
context 作为函数的第一个参数
-
永远不要存储 context 到 struct 中
-
不传递 nil context
-
一定要调用 cancel()
❌ 禁止行为
| 行为 | 原因 |
|---|---|
| 用 context 传递业务配置 | 破坏语义 |
| 在 struct 中保存 context | 生命周期混乱 |
| 忽略 cancel | 造成 goroutine 泄漏 |
八、Context 与并发模型的关系
| 机制 | 关系 |
|---|---|
| goroutine | context 控制生命周期 |
| channel | Done()本质是 channel |
| select | 监听 context 取消 |
| timeout | 防止资源耗尽 |
九、常见面试点总结
| 问题 | 答案要点 |
|---|---|
| context 能取消 goroutine 吗 | 不能,只是通知 |
| WithCancel 会释放资源吗 | 会回收 cancelCtx |
| Value 是并发安全的吗 | 是 |
| Done() 返回的 channel 能关闭多次吗 | 不会,只关闭一次 |
十、一句话总结
Context 是 Go 并发编程中的"控制平面",负责传递取消、超时和请求作用域数据,而不是业务数据。