Golang中的Context包详解与应用实践

Golang中的Context包详解与应用实践 有没有关于 context 包的好教程?你们是否将上下文同时用作数据存储以及取消/超时检查?

2 回复

关于Golang Context包的详解与应用实践

Context包的核心用途

Go的context包主要用于管理goroutine的生命周期和跨API边界的请求作用域数据。其主要功能包括:

  1. 取消传播 - 通过树状结构传播取消信号
  2. 超时控制 - 设置截止时间或超时时间
  3. 值传递 - 在请求链中传递请求作用域的值

教程推荐

官方文档是最佳起点,此外推荐:

  • Go官方博客的"Go Concurrency Patterns: Context"
  • Dave Cheney的context使用指南
  • Uber的Go风格指南中关于context的部分

Context作为数据存储的实践

虽然context可以存储值,但应谨慎使用。主要适用于:

  • 请求ID、跟踪ID等传递性数据
  • 认证信息(用户ID、权限等)
  • 其他请求作用域的元数据
package main

import (
    "context"
    "fmt"
    "time"
)

type key string

const (
    requestIDKey key = "requestID"
    userIDKey   key = "userID"
)

func main() {
    // 创建带有值的context
    ctx := context.WithValue(context.Background(), requestIDKey, "req-123")
    ctx = context.WithValue(ctx, userIDKey, "user-456")
    
    // 传递context给处理函数
    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    // 从context获取值
    if reqID, ok := ctx.Value(requestIDKey).(string); ok {
        fmt.Printf("Processing request: %s\n", reqID)
    }
    
    if userID, ok := ctx.Value(userIDKey).(string); ok {
        fmt.Printf("User: %s\n", userID)
    }
}

取消与超时的实际应用

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // 示例1:带超时的context
    ctx1, cancel1 := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel1()
    
    go worker(ctx1, "worker1")
    
    // 示例2:手动取消的context
    ctx2, cancel2 := context.WithCancel(context.Background())
    go worker(ctx2, "worker2")
    
    time.Sleep(1 * time.Second)
    cancel2() // 手动取消worker2
    
    // 示例3:截止时间的context
    deadline := time.Now().Add(3 * time.Second)
    ctx3, cancel3 := context.WithDeadline(context.Background(), deadline)
    defer cancel3()
    
    go worker(ctx3, "worker3")
    
    time.Sleep(4 * time.Second)
}

func worker(ctx context.Context, name string) {
    for {
        select {
        case <-time.After(500 * time.Millisecond):
            fmt.Printf("%s: working...\n", name)
        case <-ctx.Done():
            fmt.Printf("%s: %v\n", name, ctx.Err())
            return
        }
    }
}

同时使用数据存储和取消功能的完整示例

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

type key string

const traceIDKey key = "traceID"

func handler(w http.ResponseWriter, r *http.Request) {
    // 创建带超时和跟踪ID的context
    ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
    defer cancel()
    
    // 添加跟踪信息
    ctx = context.WithValue(ctx, traceIDKey, "trace-789")
    
    // 执行业务逻辑
    result := processWithContext(ctx)
    
    fmt.Fprintf(w, "Result: %s", result)
}

func processWithContext(ctx context.Context) string {
    // 检查context状态
    select {
    case <-ctx.Done():
        return fmt.Sprintf("Cancelled: %v", ctx.Err())
    default:
        // 获取跟踪ID
        if traceID, ok := ctx.Value(traceIDKey).(string); ok {
            return fmt.Sprintf("Processing with traceID: %s", traceID)
        }
        return "Processing without traceID"
    }
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

最佳实践建议

  1. context作为函数第一个参数
func Process(ctx context.Context, data string) error
  1. 避免存储大量数据 - context不是通用数据存储

  2. 使用自定义类型作为key,避免字符串冲突

  3. 总是检查Done()通道,及时响应取消信号

  4. 传递context时使用WithCancel/WithTimeout派生新context

在实际项目中,context确实同时用于数据传递和生命周期管理,但需遵循上述原则以确保代码的可维护性和正确性。

回到顶部