发布时间:
Go 语言的 context
包提供了一种在多个 goroutine 之间传递"上下文"的机制,主要用于控制并发任务的生命周期,包括设置超时时间、取消操作以及传递请求范围的元数据。
核心功能:Deadlines(截止时间)与 Cancellations(取消) #
context
包的核心是 Context
接口,它定义了以下关键方法:
Deadline()
:返回上下文的截止时间(若设置)Done()
:返回一个 channel,当上下文被取消或超时会关闭Err()
:返回上下文被取消的原因Value(key interface{}) interface{}
:获取上下文携带的值
常用的 Context
实现:
context.Background()
:根上下文,通常作为所有上下文的起点context.TODO()
:临时上下文,用于不确定使用哪种上下文的场景context.WithCancel(parent)
:创建可取消的上下文context.WithDeadline(parent, time)
:创建带截止时间的上下文context.WithTimeout(parent, duration)
:创建带超时的上下文context.WithValue(parent, key, value)
:创建携带键值对的上下文
常见使用场景(Common Usecases) #
1. 超时控制(Timeout) #
限制操作的执行时间,避免长时间阻塞。
context timeout context_cancel.go
package main
import (
"context"
"fmt"
"time"
)
// 模拟一个可能耗时的操作
func longRunningTask(ctx context.Context) error {
select {
case <-time.After(3 * time.Second): // 模拟任务耗时3秒
fmt.Println("任务完成")
return nil
case <-ctx.Done(): // 监听上下文取消信号
return fmt.Errorf("任务被取消: %v", ctx.Err())
}
}
func main() {
// 创建一个2秒后超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // 确保资源释放
fmt.Println("开始执行任务,超时时间2秒")
err := longRunningTask(ctx)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("程序正常结束")
}
运行结果会显示任务因超时而被取消,因为任务需要3秒而超时设置为2秒。
2. 取消操作(Cancellation) #
手动取消一组相关的 goroutine,例如用户终止请求时。
context cancel context_cancel.go
package main
import (
"context"
"fmt"
"time"
)
// 工作函数,监听上下文取消信号
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("工作者 %d 收到取消信号: %v\n", id, ctx.Err())
return
default:
fmt.Printf("工作者 %d 正在工作...\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
// 创建可取消的上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保程序退出时取消
// 启动3个工作者
for i := 1; i <= 3; i++ {
go worker(ctx, i)
}
// 运行2秒后手动取消
time.Sleep(2 * time.Second)
fmt.Println("主程序:开始取消所有工作者")
cancel()
// 等待所有工作者退出
time.Sleep(1 * time.Second)
fmt.Println("主程序:所有工作者已退出")
}
执行后会看到所有工作者在收到取消信号后停止工作。
3. 传递请求范围的元数据 #
在处理请求的整个调用链中传递数据(如请求ID、用户认证信息等)。
context value context_value.go
package main
import (
"context"
"fmt"
)
// 定义类型化的key,避免键冲突
type contextKey string
const RequestIDKey contextKey = "requestID"
// 模拟处理链中的函数
func handleRequest(ctx context.Context) {
process1(ctx)
}
func process1(ctx context.Context) {
process2(ctx)
}
func process2(ctx context.Context) {
// 从上下文中获取请求ID
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
fmt.Printf("处理请求,ID: %s\n", reqID)
} else {
fmt.Println("未找到请求ID")
}
}
func main() {
// 创建携带请求ID的上下文
ctx := context.WithValue(context.Background(), RequestIDKey, "REQ-12345")
// 处理请求
handleRequest(ctx)
}
最佳实践 #
- 不要将
Context
存储在结构体中,应作为函数参数传递 - 优先使用
context.Background()
作为根上下文 Context
是线程安全的,可以被多个 goroutine 同时使用- 避免使用
WithValue
传递大量数据,它主要用于请求范围的元数据 - 及时调用
cancel
函数释放资源,通常使用defer
确保
context
包是 Go 并发编程中控制 goroutine 生命周期的重要工具,尤其在分布式系统、服务端编程中广泛用于处理超时、取消和请求跟踪。