go 语言中的context 解读和用法
下面从设计动机 → 核心接口 → 内部结构 → 常见用法 → 典型坑点 五个层次,系统解读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 }各方法含义
方法 | 作用 |
|---|---|
| 返回 context 的截止时间 |
| 返回一个只读 channel,context 被取消时关闭 |
| 返回取消原因 |
| 获取上下文中的请求作用域数据 |
三、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 |
|
select | 监听 context 取消 |
timeout | 防止资源耗尽 |
九、常见面试点总结
问题 | 答案要点 |
|---|---|
context 能取消 goroutine 吗 | 不能,只是通知 |
WithCancel 会释放资源吗 | 会回收 cancelCtx |
Value 是并发安全的吗 | 是 |
Done() 返回的 channel 能关闭多次吗 | 不会,只关闭一次 |
十、一句话总结
Context 是 Go 并发编程中的“控制平面”,负责传递取消、超时和请求作用域数据,而不是业务数据。
