学习目标
- 理解 context 在 Go 并发中的设计初衷
- 掌握 context 的取消、超时与截止时间机制
- 理解 context 在 goroutine 树中的传播方式
- 学会用 context 管理 goroutine 的生命周期
一、为什么需要 context
在前面的并发章节中,你已经看到几个现实问题:
- goroutine 可能永久阻塞,无法退出
- 请求超时后,后台 goroutine 仍在运行
- 调用链很深时,如何统一取消所有子任务
这些问题的统一解决方案,就是 context。
context 的核心目标不是“传值”,而是:
- 取消信号的传递
- 超时 / 截止时间控制
- 请求级生命周期管理
工程认知:context 是并发世界里的“生命线”。
二、context 的基本使用方式
context 定义在标准库:
import "context"最常见的起点:
ctx := context.Background()或:
ctx := context.TODO()说明:
- Background:顶层 context,通常用于 main / server 启动
- TODO:占位用,表示“将来会补上正确的 context”
三、context 的取消机制(WithCancel)
创建一个可取消的 context:
ctx, cancel := context.WithCancel(context.Background())取消 context:
cancel()监听取消信号:
select {
case <-ctx.Done():
fmt.Println("context cancelled")
return
default:
// 继续工作
}关键点:
ctx.Done()返回一个 channel- context 被取消时,该 channel 会被关闭
四、context 的超时与截止时间
WithTimeout:相对时间
ctx, cancel := context.WithTimeout(
context.Background(),
time.Second,
)
defer cancel()WithDeadline:绝对时间
ctx, cancel := context.WithDeadline(
context.Background(),
time.Now().Add(time.Second),
)
defer cancel()两者效果类似:
- 时间到达后自动触发取消
ctx.Done()会被关闭
工程习惯:所有带超时的 context 都应该 defer cancel()。
五、context 的层级传播(父子关系)
context 是一棵树结构:
- 子 context 继承父 context
- 父 context 被取消,所有子 context 都会被取消
- 子 context 取消,不影响父 context
示例:
parent := context.Background()
ctx1, cancel1 := context.WithCancel(parent)
ctx2, cancel2 := context.WithCancel(ctx1)
cancel1() // ctx1 和 ctx2 都会被取消
工程意义:一次取消,整条 goroutine 链同时退出。
六、context 在 goroutine 中的典型用法
context 通常作为函数的第一个参数:
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("worker exit")
return
default:
// do work
}
}
}启动 goroutine:
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
// 某个条件触发
cancel()
工程规则:
- 不要在 goroutine 内创建“新的根 context”
- context 应该从上层一路传下来
七、context.WithValue 的正确定位
context 可以携带少量请求级数据:
ctx = context.WithValue(ctx, "requestID", "abc123")取值:
id := ctx.Value("requestID")重要警告:
- WithValue 不是用来传业务参数的
- 只能存放请求范围内的元数据(traceID、userID)
- key 必须使用自定义类型,避免冲突
推荐写法:
type ctxKeyRequestID struct{}
ctx = context.WithValue(ctx, ctxKeyRequestID{}, "abc123")
八、context 的常见误区
- 把 context 存进 struct 长期保存
- 在函数内部创建新的 Background
- 忘记调用 cancel 导致资源泄漏
- 用 context 传大量业务数据
工程结论:context 是控制信号,不是数据容器。
九、context 的工程定位总结
- context 统一管理 goroutine 生命周期
- context 是并发取消与超时的标准方式
- context 是现代 Go 服务的基础设施
一句话总结:
没有 context 的并发代码,迟早会失控。
预告:Day 16|并发模式与实践(worker pool / fan-in / fan-out)
下一天将系统讲解:
- worker pool 模式
- fan-in / fan-out 模式
- 如何限制并发度
- 把前面所有并发工具组合成“可控并发”