Go context 介绍
在 Go 编程语言中,context
包提供了一个用于在 goroutine 之间传递上下文信息的方法。它通常用于控制 goroutine 的生命周期、传递请求范围内的数据、以及处理超时或取消信号。context
包的核心是 Context
接口和与之相关的函数和方法。
Context
接口
Context
接口定义如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline()
返回上下文会被自动取消的时间,以及是否存在这样的时间。Done()
返回一个chan struct{}
,当上下文被取消或超时时,这个通道会被关闭。Err()
返回上下文被取消的原因。如果Done
通道已经关闭,它会返回一个非 nil 的错误。Value(key interface{})
返回与上下文关联的键对应的值。
创建 Context
通常有四种方式来创建一个 Context
:
context.Background()
context.TODO()
context.WithCancel(parent Context)
context.WithTimeout(parent Context, timeout time.Duration)
context.WithDeadline(parent Context, deadline time.Time)
context.WithValue(parent Context, key, val interface{})
例子
使用 context.Background()
context.Background()
返回一个空的上下文,通常用于主函数、初始化和测试。
ctx := context.Background()
使用 context.WithCancel
context.WithCancel
返回一个派生上下文和一个取消函数。当调用取消函数时,派生上下文的 Done
通道会被关闭。
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保资源被释放
go func() {
// 模拟一些工作
time.Sleep(2 * time.Second)
cancel() // 取消上下文
}()
<-ctx.Done() // 等待上下文被取消
fmt.Println("Context canceled:", ctx.Err())
使用 context.WithTimeout
context.WithTimeout
返回一个派生上下文和一个取消函数。指定的超时时间过后,派生上下文的 Done
通道会被关闭。
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
select {
case <-time.After(5 * time.Second):
fmt.Println("Operation completed")
case <-ctx.Done():
fmt.Println("Timeout:", ctx.Err())
}
使用 context.WithValue
context.WithValue
返回一个带有键值对的派生上下文,用于在请求的生命周期中传递数据。
type keyType string
func main() {
ctx := context.WithValue(context.Background(), keyType("userID"), 12345)
ProcessRequest(ctx)
}
func ProcessRequest(ctx context.Context) {
userID := ctx.Value(keyType("userID")).(int)
fmt.Println("User ID:", userID)
}
典型应用场景
- API 请求处理: 在处理 HTTP 请求时,传递上下文以管理超时和取消。
- 并发任务管理: 使用上下文来控制和取消多个并发任务。
- 传递请求范围的数据: 例如用户身份验证信息、跟踪 ID 等。
注意事项
Context
是不可变的,应当通过context.WithCancel
,context.WithTimeout
,context.WithDeadline
,context.WithValue
等函数创建新的派生上下文。- 不要将
Context
存储在结构体中,应当作为函数的第一个参数传递。 - 尽量在短生命周期的请求中使用
Context
,避免长时间持有上下文。
通过正确使用 context
包,可以编写出更健壮、更易于维护的并发程序。