怎么理解GO中的context
怎么理解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 mainimport ("context""fmt""time"
)func dbQuery(ctx context.Context) error {// 模拟慢 SQLdone := 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 mainimport ("context""fmt""os""os/signal""time"
)func worker(ctx context.Context) {for {select {case <-ctx.Done():fmt.Println("worker 收到取消信号,收尾退出")returndefault: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)<-ccancel()}()worker(ctx)
}
运行后按 Ctrl+C
,worker 会优雅退出。
3. 传值场景:把 trace-id 从 HTTP 中间件传到 DAO 层
type key int
const traceKey key = iotafunc 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()
清理,防止泄漏;接口方法仍然存在,但上下文逻辑生命周期结束,不得再用于派生或传递