--- tags: [go, golang, Context, 并发, 超时控制] create time: 2026-06-07 14:55 --- # Context ## 概述 Context 是 Go 中跨 goroutine 传递取消信号、截止时间、请求作用域值的标准机制。它是构建可取消、可超时、可追踪的并发程序的核心工具。 ## 正文 ### Context 接口 ```go type Context interface { Deadline() (deadline time.Time, ok bool) // 截止时间 Done() <-chan struct{} // 取消信号通道 Err() error // 取消原因 Value(key interface{}) interface{} // 键值对存储 } ``` > [!question] 💭 思考 > 当一个 HTTP 请求被客户端断开连接时,如何通知所有正在处理的 goroutine 立即停止? 四个方法各司其职: - `Deadline`:返回截止时间(如果设置了) - `Done`:返回只读 channel,关闭时表示应停止工作 - `Err`:返回取消原因(`context.Canceled` 或 `context.DeadlineExceeded`) - `Value`:获取请求级别的上下文信息(如 traceID、用户信息) ### 创建根 Context ```go ctx := context.Background() // 空 context,不可取消,作为所有 context 的祖先 ctx := context.TODO() // 不确定该用哪个 context 时的占位 ``` > [!note] 📝 Background vs TODO > - `Background()`:已知没有父 context 时使用(如 main 函数入口) > - `TODO()`:暂时不知道用什么 context,后续会替换——相当于"先写代码,稍后完善" ### Context 派生树 > [!info] ℹ️ Context 的传播链 > Context 通过 With 系列函数层层派生,形成一棵树。取消一个父 context,所有子 context 都会收到取消信号。 ```mermaid flowchart TD BG["background"] --> C1["WithCancel"] C1 --> C2["WithTimeout"] C1 --> C3["WithValue"] C2 --> C4["WithValue"] style BG fill:#e8f5e9 style C1 fill:#fff3e0 style C2 fill:#e3f2fd style C3 fill:#fce4ec style C4 fill:#f3e5f5 ``` | 派生函数 | 功能 | 典型场景 | |----------|------|---------| | `WithCancel` | 手动取消 | 业务逻辑主动终止 | | `WithTimeout` | 超时自动取消 | RPC 调用、HTTP 请求 | | `WithDeadline` | 指定时刻取消 | 定时任务截止 | | `WithValue` | 传递请求级数据 | traceID、用户认证信息 | ### 并发控制模式 #### WithCancel — 手动取消 ```go func main() { ctx, cancel := context.WithCancel(context.Background()) go watch(ctx, "observer1") go watch(ctx, "observer2") time.Sleep(5 * time.Second) cancel() // 通知所有监听者退出 time.Sleep(time.Second) } func watch(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Printf("%s: %v\n", name, ctx.Err()) return default: fmt.Printf("%s working...\n", name) time.Sleep(time.Second) } } } ``` > [!tip] 💡 必记习惯:cancel 函数要调用 > ```go > ctx, cancel := context.WithTimeout(parent, 5*time.Second) > defer cancel() // 延迟释放资源,即使提前返回也不会泄漏 > ``` #### WithTimeout / WithDeadline ```go // 超时 5 秒 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 截止到明天中午 12 点 ctx, cancel := context.WithDeadline(context.Background(), tomorrowNoon) defer cancel() ``` > [!note] 📝 Timeout vs Deadline > - `WithTimeout(d)` = 从现在开始经过 d 时间后超时 > - `WithDeadline(t)` = 在绝对时间点 t 超时 > - 大多数情况下使用 `WithTimeout` 更直观 #### WithValue — 传递请求级数据 ```go type contextKey string const userIDKey contextKey = "userID" func handler(w http.ResponseWriter, r *http.Request) { // 从请求头获取用户 ID,放入 context ctx := context.WithValue(r.Context(), userIDKey, getUserID(r)) // 传递给下游 goroutine processRequest(ctx) } func processRequest(ctx context.Context) { if id := ctx.Value(userIDKey); id != nil { log.Printf("processing for user %v", id) } } ``` > [!warning] ⚠️ Value 的使用禁忌 > 1. **不要用 Context 传递可选参数**——那是函数参数的职责 > 2. **key 不要使用 string/int 类型**——容易冲突,自定义 type 作为 key > 3. **Value 是不可变的**——子 context 只能读取不能修改父的值 > 4. **不用于传递业务核心数据**——这些数据应该作为函数参数显式传递 ### 完整实战:带超时的 RPC 链路 ```go func handleRequest(w http.ResponseWriter, r *http.Request) { // 1. 基于请求创建 context(携带超时和 traceID) ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() ctx = context.WithValue(ctx, traceIDKey, generateTraceID()) // 2. 并行调用多个下游服务 resultCh := make(chan Result, 2) go func() { resultCh <- callServiceA(ctx) }() go func() { resultCh <- callServiceB(ctx) }() // 3. 等待结果或超时 select { case r1 := <-resultCh: handleResult(r1) case r2 := <-resultCh: handleResult(r2) case <-ctx.Done(): http.Error(w, "request timeout", http.StatusGatewayTimeout) } } ``` > [!warning] ⚠️ Context 传播的最佳实践 > - Context 必须作为函数的**第一个参数**,命名为 `ctx` > - 永远用传入的 context 创建新的 context,不要自己新建 background > - 所有网络调用、DB 查询都应接受 context 并检查取消信号 > - 不要在 goroutine 中忽略 context.Done()——否则会导致 goroutine leak ## 关联笔记 - [[hzh/GolangStar/Go语言进阶/Select]] - [[hzh/GolangStar/Go语言进阶/Goroutine]] - [[hzh/GolangStar/Go语言原理/context原理]]