全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-13 8 分钟 ✍️ juanwangdev

Go Context上下文控制详解

Context是Go并发控制的核心机制,用于传递请求范围数据和控制goroutine。

Context接口

Go
type Context interface {
    Deadline() (deadline time.Time, ok bool)  // 返回截止时间
    Done() <-chan struct{}                    // 返回取消信号
    Err() error                               // 返回取消原因
    Value(key interface{}) interface{}        // 返回关联值
}

Context创建

Background根Context

Go
// 根Context,永不取消
ctx := context.Background()

// 通常作为起点
func main() {
    ctx := context.Background()
    handleRequest(ctx)
}

TODO占位Context

Go
// 未确定具体Context时使用
ctx := context.TODO()
// 代码审查时会提醒完善

取消控制

WithCancel手动取消

Go
func operation(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("取消:", ctx.Err())
            return
        default:
            // 执行工作
            time.Sleep(100 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    go operation(ctx)

    time.Sleep(500 * time.Millisecond)
    cancel()  // 取消

    time.Sleep(100 * time.Millisecond)
}

WithTimeout超时取消

Go
func slowOperation(ctx context.Context) error {
    select {
    case <-time.After(3 * time.Second):
        return nil
    case <-ctx.Done():
        return ctx.Err()  // context deadline exceeded
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    if err := slowOperation(ctx); err != nil {
        fmt.Println("超时:", err)
    }
}

WithDeadline截止时间

Go
deadline := time.Now().Add(2 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()

// 到达deadline时自动取消

传递值

WithValue传递数据

Go
func main() {
    ctx := context.WithValue(context.Background(), "userID", 123)

    handleRequest(ctx)
}

func handleRequest(ctx context.Context) {
    userID := ctx.Value("userID")
    if userID != nil {
        fmt.Println("用户ID:", userID)
    }
}

Value用于传递请求范围数据,不要滥用传递可选参数。

取消传播机制

子Context继承取消

Go
func main() {
    parent, cancel := context.WithCancel(context.Background())

    // 子Context继承parent的取消
    child, _ := context.WithCancel(parent)

    go func() {
        select {
        case <-child.Done():
            fmt.Println("子收到取消")
        }
    }()

    cancel()  // 取消parent
    // child也被取消
}

传播链

Go
Background → WithCancel → WithTimeout → WithValue
                  ↓             ↓
              子Context    孙Context

取消parent → 所有子Context收到Done信号

HTTP请求超时

客户端超时

Go
func fetchWithTimeout(url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

服务端处理

Go
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()  // 从请求获取Context

    // 客户端断开时ctx自动取消
    err := processRequest(ctx)
    if err == context.Canceled {
        fmt.Println("客户端断开")
    }
}

Context使用规则

1. 不要存储Context

Go
// 错误:结构体存储Context
type Service struct {
    ctx context.Context  // 不要这样做
}

// 正确:函数参数传递
func (s *Service) Do(ctx context.Context) {
}

2. Context作为第一个参数

Go
// 标准函数签名
func Do(ctx context.Context, arg Arg) error

// 而非
func Do(arg Arg, ctx context.Context) error

3. 不要传递nil Context

Go
// 错误
func Do(ctx context.Context) {
    if ctx == nil {
        ctx = context.Background()  // 不推荐
    }
}

// 正确:调用者传递有效Context
Do(context.Background())

4. 调用cancel释放资源

text
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel()  // 必须调用,即使操作完成

// 原因:cancel释放定时器等资源

Context类型对比

创建函数用途取消方式
Background根Context不取消
TODO占位不取消
WithCancel手动取消cancel()
WithTimeout超时到达时间
WithDeadline截止时间到达时间
WithValue传递值继承parent

要点总结

  • Background是根Context,永不取消
  • WithCancel手动取消,调用cancel()释放资源
  • WithTimeout设置超时,自动取消
  • WithDeadline设置截止时间点
  • WithValue传递请求范围数据
  • 取消信号向下传播给所有子Context
  • Context作为函数第一个参数
  • 不要在结构体中存储Context
  • 不要传递nil Context
  • defer cancel()确保资源释放

📝 发现内容有误?点击此处直接编辑

← 上一篇 Go Channel同步与多路复用详解
下一篇 → Go Goroutine与并发模型详解
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库