Skip to content

Context

Context 是 Go 1.7 引入的标准库,用于在 Goroutine 之间传递请求范围的数据、取消信号和超时。

Context 基础

context 包

go
import "context"

Context 接口

go
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

创建 Context

根 Context

go
// 创建根 Context
ctx := context.Background()

// 或者使用 TODO(用于不确定的场景)
ctx := context.TODO()

带取消的 Context

go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()  // 确保取消

// 在另一个 Goroutine 中取消
go func() {
    time.Sleep(2 * time.Second)
    cancel()
}()

// 等待取消
<-ctx.Done()
fmt.Println("Context 已取消")

带超时的 Context

go
// 超时 Context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// 使用
select {
case <-ctx.Done():
    fmt.Println("超时")
case <-time.After(10 * time.Second):
    fmt.Println("完成")
}

带截止时间的 Context

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

带值的 Context

go
ctx := context.WithValue(context.Background(), "userID", 123)
userID := ctx.Value("userID")

Context 使用场景

1. 请求取消

go
func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Worker 被取消")
            return
        default:
            // 执行工作
            fmt.Println("工作中...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    
    go worker(ctx)
    
    time.Sleep(2 * time.Second)
    cancel()  // 取消 Worker
    
    time.Sleep(1 * time.Second)
}

2. 超时控制

go
func fetchData(ctx context.Context) (string, error) {
    // 模拟网络请求
    select {
    case <-time.After(3 * time.Second):
        return "数据", nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    data, err := fetchData(ctx)
    if err != nil {
        fmt.Println("错误:", err)  // context deadline exceeded
        return
    }
    fmt.Println("数据:", data)
}

3. 值传递

go
type key string

const userIDKey key = "userID"
const userNameKey key = "userName"

func setUser(ctx context.Context, id int, name string) context.Context {
    ctx = context.WithValue(ctx, userIDKey, id)
    ctx = context.WithValue(ctx, userNameKey, name)
    return ctx
}

func getUser(ctx context.Context) (int, string) {
    id := ctx.Value(userIDKey).(int)
    name := ctx.Value(userNameKey).(string)
    return id, name
}

func main() {
    ctx := context.Background()
    ctx = setUser(ctx, 123, "Alice")
    
    id, name := getUser(ctx)
    fmt.Printf("用户 ID: %d, 用户名: %s\n", id, name)
}

Context 传播

在函数间传递

go
func handler(ctx context.Context) {
    // 传递 Context 给其他函数
    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    // 检查 Context 是否已取消
    if ctx.Err() != nil {
        return
    }
    
    // 继续处理
    doWork(ctx)
}

在 HTTP 请求中使用

go
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    
    // 创建带超时的 Context
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    // 传递给业务逻辑
    result, err := businessLogic(ctx)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Write([]byte(result))
}

Context 最佳实践

1. Context 作为第一个参数

go
// 好的做法
func doSomething(ctx context.Context, param string) error {
    // ...
}

// 不好的做法
func doSomething(param string, ctx context.Context) error {
    // ...
}

2. 不要存储 Context

go
// 不好的做法
type Server struct {
    ctx context.Context
}

// 好的做法:每次从参数传递
func (s *Server) Handle(ctx context.Context, req *Request) {
    // ...
}

3. 使用类型安全的键

go
// 不好的做法
ctx := context.WithValue(ctx, "userID", 123)

// 好的做法
type key string
const userIDKey key = "userID"
ctx := context.WithValue(ctx, userIDKey, 123)

4. 检查 Context 状态

go
func process(ctx context.Context) error {
    // 检查是否已取消
    if ctx.Err() != nil {
        return ctx.Err()
    }
    
    // 继续处理
    return nil
}

完整示例

go
package main

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

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d 被取消: %v\n", id, ctx.Err())
            return
        default:
            fmt.Printf("Worker %d 工作中...\n", id)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func fetchData(ctx context.Context, url string) (string, error) {
    // 模拟网络请求
    select {
    case <-time.After(2 * time.Second):
        return fmt.Sprintf("数据来自 %s", url), nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

func main() {
    // 示例 1: 取消
    ctx1, cancel1 := context.WithCancel(context.Background())
    go worker(ctx1, 1)
    time.Sleep(2 * time.Second)
    cancel1()
    time.Sleep(500 * time.Millisecond)
    
    // 示例 2: 超时
    ctx2, cancel2 := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel2()
    
    data, err := fetchData(ctx2, "https://example.com")
    if err != nil {
        fmt.Printf("获取数据失败: %v\n", err)
    } else {
        fmt.Println("数据:", data)
    }
    
    // 示例 3: 值传递
    type key string
    const userIDKey key = "userID"
    
    ctx3 := context.WithValue(context.Background(), userIDKey, 123)
    userID := ctx3.Value(userIDKey)
    fmt.Printf("用户 ID: %v\n", userID)
}

常见模式

链式 Context

go
func middleware1(ctx context.Context) context.Context {
    return context.WithValue(ctx, "key1", "value1")
}

func middleware2(ctx context.Context) context.Context {
    return context.WithValue(ctx, "key2", "value2")
}

func handler(ctx context.Context) {
    ctx = middleware1(ctx)
    ctx = middleware2(ctx)
    // 使用 ctx
}

Context 树

go
parentCtx := context.Background()

// 创建子 Context
childCtx1, cancel1 := context.WithCancel(parentCtx)
childCtx2, cancel2 := context.WithTimeout(parentCtx, 5*time.Second)

// 取消父 Context 会影响所有子 Context
// 但取消子 Context 不会影响父 Context

下一步

接下来我们将学习:

基于 VitePress 构建