Files
cs-note/hzh/GolangStar/Go语言进阶/Context.md
T

5.7 KiB
Raw Blame History

tags, create time
tags create time
go
golang
Context
并发
超时控制
2026-06-07 14:55

Context

概述

Context 是 Go 中跨 goroutine 传递取消信号、截止时间、请求作用域值的标准机制。它是构建可取消、可超时、可追踪的并发程序的核心工具。

正文

Context 接口

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

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 都会收到取消信号。

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 — 手动取消

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 函数要调用

ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel() // 延迟释放资源,即使提前返回也不会泄漏

WithTimeout / WithDeadline

// 超时 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 — 传递请求级数据

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 链路

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

关联笔记