目录

怎么理解GO中的context

怎么理解GO中的context

[https://csdnimg.cn/release/blogv2/dist/pc/img/activeVector.png AI提效·半月创作挑战赛 10w+人浏览 465人参与

https://csdnimg.cn/release/blogv2/dist/pc/img/arrowright-line-White.png]( )

怎么理解GO中的context

一、context本质是上是go的一个接口,该接口的接口方法如下:

type Context interface {
Deadline() (deadline time.Time, ok bool) // 返回到期时间
Done() <-chan struct{} // 只读通道,关闭即代表“该结束了”
Err() error // 为什么结束:取消?超时?
Value(key interface{}) interface{} // 像请求范围的 map,传值用
}

二、具备三个核心能力:

1、取消信号(Done通道关闭)

2、超时控制(Deadline)

3、请求级键值对(Value)

三、五种工厂函数:

函数作用典型场景
context.Background()返回空 Context,永不取消,常做根节点main 函数初始化
context.TODO()和 Background 一样,但语义上表示“以后再加超时/取消”快速原型,占位
context.WithCancel(parent)返回一个可手动取消的子 Context业务逻辑主动取消
context.WithTimeout(parent, timeout)带超时定时器,时间到自动取消数据库、RPC、HTTP 客户端
context.WithDeadline(parent, t)指定绝对时间点到期凌晨 3 点批量任务
context.WithValue(parent, key, val)附加键值对,透传请求数据trace-id、用户 ID

四、最简单的实现方式:

1. 超时场景:数据库查询最多给 500 ms
package main

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

func dbQuery(ctx context.Context) error {
    // 模拟慢 SQL
    done := make(chan struct{})
    go func() {
        time.Sleep(600 * time.Millisecond) // 故意超过超时
        close(done)
    }()

    select {
    case <-ctx.Done(): // 500 ms 后这里会收到信号
        return ctx.Err() // "context deadline exceeded"
    case <-done:
        return nil
    }
}

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

    if err := dbQuery(ctx); err != nil {
        fmt.Println("查询失败:", err)
        return
    }
    fmt.Println("查询成功")
}

输出:

查询失败: context deadline exceeded
2. 手动取消:用户中途按 Ctrl+C
package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "time"
)

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("worker 收到取消信号,收尾退出")
            return
        default:
            fmt.Println("working...")
            time.Sleep(300 * time.Millisecond)
        }
    }
}

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

    // 把 Ctrl+C 转成取消信号
    go func() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, os.Interrupt)
        <-c
        cancel()
    }()

    worker(ctx)
}

运行后按 Ctrl+C,worker 会优雅退出。

3. 传值场景:把 trace-id 从 HTTP 中间件传到 DAO 层
type key int
const traceKey key = iota

func HTTPHandler(w http.ResponseWriter, r *http.Request) {
    traceID := r.Header.Get("X-Trace-ID")
    ctx := context.WithValue(r.Context(), traceKey, traceID)
    Business(ctx)
}

func Business(ctx context.Context) {
    if v := ctx.Value(traceKey); v != nil {
        fmt.Println("trace-id:", v)
    }
}

注:任何context的使用都需要调用cancle来避免内存泄漏。调用canle()的瞬间,且ctx.Done()通道被关闭,所有正在阻塞 <-ctx.Done() 的 goroutine 立刻收到“零值”,继续执行收尾逻辑;ctx.Err() 返回非空错误,值固定是 context.Canceled,告诉下游是被人主动取消的;定时器被停止;并向下广播这一状态,比如 WithTimeout 创建的 timerCtx,内部定时器会被 stopTimer() 清理,防止泄漏;接口方法仍然存在,但上下文逻辑生命周期结束,不得再用于派生或传递