在 Go(Golang)中,context 是一个标准库提供的重要机制,用于控制协程(goroutine)之间的取消、超时、截止时间传递,以及上下文数据传递。它是并发编程中管理协程生命周期和避免资源泄漏的核心工具之一。
context 的主要用途
- 取消协程(Cancellation)
- 设置超时时间或截止时间(Timeout / Deadline)
- 跨 API 传递请求范围的数据(如认证信息、trace id)
- 防止资源泄露(确保任务完成或及时退出)
context 的基本接口和实现
接口定义(简化)
1 2 3 4 5 6
| type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any }
|
四个常用的 context 构造函数
| 函数 | 说明 |
|---|
context.Background() | 最基础的 context,通常用于 main 函数、初始化或测试中 |
context.TODO() | 表示目前还不确定要用哪个 context(类似占位符) |
context.WithCancel(parent) | 返回一个新的 context 和一个取消函数,调用该函数会通知所有衍生的协程退出 |
context.WithTimeout(parent, timeout) | 指定超时自动取消 |
context.WithDeadline(parent, time.Time) | 指定某一时刻自动取消 |
context.WithValue(parent, key, val) | 传递 request-scoped 数据 |
使用示例
WithCancel 示例:手动取消任务
1 2 3 4 5 6 7 8 9 10 11
| ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) { select { case <-ctx.Done(): fmt.Println("goroutine exit:", ctx.Err()) } }(ctx)
time.Sleep(2 * time.Second) cancel()
|
WithTimeout 示例:自动超时取消
1 2 3 4 5 6 7 8 9
| ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel()
select { case <-time.After(3 * time.Second): fmt.Println("operation done") case <-ctx.Done(): fmt.Println("timeout:", ctx.Err()) }
|
WithValue 示例:传递请求数据
1 2 3 4 5 6 7
| type key string ctx := context.WithValue(context.Background(), key("userID"), 42)
func(ctx context.Context) { val := ctx.Value(key("userID")) fmt.Println("userID:", val) }(ctx)
|
⚠️ 建议使用自定义类型作为 key,防止冲突。
协程泄露的场景与 context 的意义
很多时候,我们会在协程中执行耗时任务,如果外部请求被取消或超时,协程仍然运行,就会导致资源泄露。
通过 context 的 Done() 通道可以通知协程“取消”:
1 2 3 4 5 6 7 8 9 10 11
| func doWork(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("work canceled") return default: } } }
|
实践中常见的用法
- HTTP 请求生命周期管理(Go 标准库中的
http.Request 已经自带 context) - gRPC 调用(Context 是核心机制)
- 数据库查询、Redis、消息队列等外部服务调用
- 微服务链路追踪中传递 trace ID、user token 等
注意事项和最佳实践
- 尽量用 context 控制 goroutine 生命周期
- 不要滥用
WithValue,它不是万能数据容器 - 及时调用
cancel(),避免资源泄漏 - 避免在多个 goroutine 中共享一个可变 context(除非只读)
- 每层调用传入 context,形成清晰调用链
Go 标准库中 context 的使用案例
http.Request.WithContext(ctx):用于绑定 request 生命周期sql.DB.QueryContext(ctx):执行 SQL 时可以中途取消grpc.NewServer(...opts...):服务端方法自动接收 contextexec.CommandContext(ctx, ...):运行外部命令并能中断
总结
| 特性 | 描述 |
|---|
| 可取消 | 协程间传递取消信号 |
| 超时控制 | 自动取消长时间运行的任务 |
| 数据传递 | 用于 request-scoped 数据 |
| 层级结构 | 子 context 会随着父 context 的取消而取消 |
| 通用接口 | 适用于标准库和第三方库中各种 IO、RPC、任务控制场景 |