前言
Context 是 Go 1.7 添加于标准函数库的功能。常在存取数据库或其他服务时会遇到,初步看起来是用于「传递取消信号」用途的语法,用于处理 goroutine:
- 背负期限(deadline)
- 取消信号(cancellation signal)
- 传递请求相关的值(request-scoped values)
Context 是啥?
举例当一个请求会衍生出多个 goroutine、或会调用多层函数时,常会遇到几个问题:
- 上层请求已经结束了,但底下 goroutine 还在跑
- 某个操作卡住,希望超时后能全部中断
- 想在整条调用链中携带
request id之类的信息
Go context 包了 channel 在 goroutine 间传递,定义其存在的理由(某条件或期限达成),而这个理由应该与 context 相关联。
// Context 背负期限(deadline)、取消信号(cancellation signal)、与请求相关的值(request-scoped values)// 它的方法可以被多个 goroutine 同时安全地使用。type Context interface { // Done 会返回一个 channel, // 当此 Context 被取消或逾时(timeout)时,该 channel 会被关闭。 Done() <-chan struct{}
// Err 会在 Done channel 关闭之后, // 返回此 Context 被取消的原因。 Err() error
// Deadline 会返回此 Context 预计被取消的时间(如果有设置的话)。 // ok 表示是否存在 deadline。 Deadline() (deadline time.Time, ok bool)
// Value 会返回与指定 key 关联的值; // 如果没有对应的值,则返回 nil。 Value(key interface{}) interface{}}Done() <-chan struct{} 用例
结束时 Done() channel 才会被关闭。未关闭前会返回 nil。
- Context 被取消(cancel)
- 超时(timeout)
- parent Context 结束
select {case <-ctx.Done(): // 收到取消或超时信号 return ctx.Err()case result := <-workChan: return result}Err() error 用例
当 Context 结束后得知原因。未关闭前会返回 nil。
<-ctx.Done()log.Println(ctx.Err())Deadline() (time.Time, bool) 用例
查 Context 是否有设置截止时间。
ok == false:没有 deadlineok == true:deadline 是什么时候
Value(key interface{}) interface{} 用例
Value 允许你在 Context 里存放「请求范围内」的数据,例如:
// 上游创建 contextctx = context.WithValue(ctx, "request_id", "abc-123")
// 下游读取 contextreqID := ctx.Value("request_id")与 Context 搭配的方法
Background
所有 Context 的根节点,通常用于主函数、初始化或测试中
返回一个非 nil 的空 Context,它:
- 永远不会被取消
- 没有截止时间
- 没有携带任何值
// ctx1 是根节点(Background)ctx1 := context.Background()
// ctx2 是 ctx1 的子节点ctx2 := context.WithValue(ctx1, "key", "value") // 仍然不可取消(只是带了值)
// ctx3 是 ctx2 的子节点ctx3, cancel := context.WithTimeout(ctx2, 5*time.Second) // 可以取消(有超时)defer cancel()context.Background() (永远不会被取消) │ └─> WithValue(...) (继承父节点,仍不可取消,但带了值) │ └─> WithTimeout(...) (可以被取消!5秒后自动取消) │ └─> WithCancel(...) (可以被取消!调用 cancel() 时取消)TODO
当你不确定该用哪个 Context 时的占位符
func someFunction() { // 暂时不确定要用什么 Context,先用 TODO ctx := context.TODO() anotherFunction(ctx)}WithCancel
当你希望「某个事件发生时,主动中止所有相关 goroutine」
ctx, cancel := context.WithCancel(context.Background())defer cancel() // 很重要,避免资源泄漏
go func() { time.Sleep(2 * time.Second) cancel()}()
<-ctx.Done()fmt.Println(ctx.Err()) // context.CanceledWithDeadline
当你希望「在指定的时间点之前完成,主动中止所有相关 goroutine」
// API 必须在特定时间前响应deadline := time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)ctx, cancel := context.WithDeadline(ctx, deadline)defer cancel()
resp, err := client.DoWithContext(ctx, req)WithTimeout
当你希望「某个期限过后,主动中止所有相关 goroutine」
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()
select {case <-time.After(5 * time.Second): fmt.Println("finished work")case <-ctx.Done(): fmt.Println(ctx.Err()) // context.DeadlineExceeded}WithValue
Context 数值应基于 request 范围内的数据传输过程和 API 数据
// 上游创建 contextctx := context.Background()ctx = context.WithValue(ctx, "request_id", "abc-123")
// 下游读取 contextreqID := ctx.Value("request_id")// 创建私有类型避免 key 撞名type ctxKeyRequestID struct{}ctx = context.WithValue(ctx, ctxKeyRequestID{}, "abc-123")总结
Go Context 是一种官方方式去管理 Go Routine 的生命周期基于 Channel
目前我只在与 DB 互动上使用到这项功能,或许之后会遇到更多情境帮得上忙。就是一种官方的方式管理 Go Routine 的生命周期。
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()
var todos []models.Todocursor, err := db.GetCollection().Find(ctx, bson.M{})延伸阅读
- Learning Golang Context!! Never Looked At It! - TheVimeagen
- Go Concurrency Patterns: Context - Go
- Go From Zero to Depth — Part 8: Context Isn’t Cancellation — It’s a Protocol