Context #

一、Context概述 #

Context用于在goroutine之间传递取消信号、超时、截止时间和请求范围的值。

二、创建Context #

2.1 Background #

go
ctx := context.Background()

作为根context,通常在main函数或测试中使用。

2.2 TODO #

go
ctx := context.TODO()

当不确定使用哪个context时使用。

2.3 WithCancel #

go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
    <-ctx.Done()
    fmt.Println("Canceled")
}()

time.Sleep(time.Second)
cancel()

2.4 WithTimeout #

go
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

select {
case <-ctx.Done():
    fmt.Println("Timeout:", ctx.Err())
}

2.5 WithDeadline #

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

select {
case <-ctx.Done():
    fmt.Println("Deadline reached")
}

2.6 WithValue #

go
ctx := context.WithValue(context.Background(), "key", "value")
v := ctx.Value("key")
fmt.Println(v)  // value

三、Context接口 #

3.1 接口定义 #

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

3.2 方法说明 #

方法 说明
Deadline 返回截止时间
Done 返回取消通道
Err 返回取消原因
Value 获取关联值

四、取消机制 #

4.1 主动取消 #

go
func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Worker stopped")
            return
        default:
            // 工作
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx)
    
    time.Sleep(time.Second)
    cancel()
    time.Sleep(time.Millisecond * 100)
}

4.2 级联取消 #

go
func main() {
    ctx1, cancel1 := context.WithCancel(context.Background())
    ctx2, _ := context.WithCancel(ctx1)
    
    go func() {
        <-ctx2.Done()
        fmt.Println("ctx2 canceled")
    }()
    
    cancel1()  // ctx2也会被取消
}

五、超时控制 #

5.1 HTTP请求超时 #

go
func fetch(url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    return io.ReadAll(resp.Body)
}

5.2 数据库查询超时 #

go
func queryWithTimeout(db *sql.DB, query string) (*sql.Rows, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    
    return db.QueryContext(ctx, query)
}

5.3 操作超时 #

go
func operation(ctx context.Context) error {
    select {
    case <-time.After(2 * time.Second):
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    
    if err := operation(ctx); err != nil {
        fmt.Println("Error:", err)
    }
}

六、值传递 #

6.1 请求ID #

go
type key string

const requestIDKey key = "requestID"

func WithRequestID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, requestIDKey, id)
}

func GetRequestID(ctx context.Context) string {
    if v := ctx.Value(requestIDKey); v != nil {
        return v.(string)
    }
    return ""
}

6.2 用户信息 #

go
type User struct {
    ID   int
    Name string
}

const userKey key = "user"

func WithUser(ctx context.Context, user *User) context.Context {
    return context.WithValue(ctx, userKey, user)
}

func GetUser(ctx context.Context) *User {
    if v := ctx.Value(userKey); v != nil {
        return v.(*User)
    }
    return nil
}

6.3 注意事项 #

  • 不要用于传递可选参数
  • 使用自定义类型作为key避免冲突
  • 值应该是不可变的

七、实际应用 #

7.1 HTTP中间件 #

go
func WithTimeoutMiddleware(timeout time.Duration) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx, cancel := context.WithTimeout(r.Context(), timeout)
            defer cancel()
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

7.2 优雅关闭 #

go
func main() {
    server := &http.Server{Addr: ":8080"}
    
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatal(err)
        }
    }()
    
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    server.Shutdown(ctx)
}

7.3 并发控制 #

go
func processAll(ctx context.Context, items []int) error {
    var wg sync.WaitGroup
    errCh := make(chan error, len(items))
    
    for _, item := range items {
        wg.Add(1)
        go func(item int) {
            defer wg.Done()
            if err := process(ctx, item); err != nil {
                errCh <- err
            }
        }(item)
    }
    
    done := make(chan struct{})
    go func() {
        wg.Wait()
        close(done)
    }()
    
    select {
    case err := <-errCh:
        return err
    case <-done:
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

八、最佳实践 #

8.1 函数第一个参数 #

go
func DoSomething(ctx context.Context, arg Arg) error {
    // ...
}

8.2 不要存储Context #

go
// 错误
type MyStruct struct {
    ctx context.Context
}

// 正确:显式传递
func (s *MyStruct) Do(ctx context.Context) {}

8.3 调用cancel #

go
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()  // 释放资源

8.4 不要传递nil #

go
// 错误
func Do(ctx context.Context) {}
Do(nil)

// 正确
Do(context.Background())

九、常见错误 #

9.1 忘记调用cancel #

go
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
// 忘记defer cancel()

9.2 阻塞Done通道 #

go
select {
case <-ctx.Done():
    // 不要在这里阻塞
}

9.3 滥用WithValue #

go
// 不要用于传递可选参数
ctx = context.WithValue(ctx, "timeout", time.Second)

十、总结 #

Context要点:

函数 用途
Background 根context
TODO 占位context
WithCancel 可取消context
WithTimeout 超时context
WithDeadline 截止时间context
WithValue 携带值的context

关键点:

  1. 取消传播:取消信号会级联传播
  2. 超时控制:用于控制操作超时
  3. 值传递:传递请求范围的数据
  4. 调用cancel:必须调用cancel释放资源
  5. 第一个参数:context作为函数第一个参数

准备好学习Channel了吗?让我们进入下一章!

最后更新:2026-03-26