Go Context上下文控制详解
Context是Go并发控制的核心机制,用于传递请求范围数据和控制goroutine。
Context接口
Go
type Context interface {
Deadline() (deadline time.Time, ok bool) // 返回截止时间
Done() <-chan struct{} // 返回取消信号
Err() error // 返回取消原因
Value(key interface{}) interface{} // 返回关联值
}
Context创建
Background根Context
Go
// 根Context,永不取消
ctx := context.Background()
// 通常作为起点
func main() {
ctx := context.Background()
handleRequest(ctx)
}
TODO占位Context
Go
// 未确定具体Context时使用
ctx := context.TODO()
// 代码审查时会提醒完善
取消控制
WithCancel手动取消
Go
func operation(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("取消:", ctx.Err())
return
default:
// 执行工作
time.Sleep(100 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go operation(ctx)
time.Sleep(500 * time.Millisecond)
cancel() // 取消
time.Sleep(100 * time.Millisecond)
}
WithTimeout超时取消
Go
func slowOperation(ctx context.Context) error {
select {
case <-time.After(3 * time.Second):
return nil
case <-ctx.Done():
return ctx.Err() // context deadline exceeded
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
if err := slowOperation(ctx); err != nil {
fmt.Println("超时:", err)
}
}
WithDeadline截止时间
Go
deadline := time.Now().Add(2 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
// 到达deadline时自动取消
传递值
WithValue传递数据
Go
func main() {
ctx := context.WithValue(context.Background(), "userID", 123)
handleRequest(ctx)
}
func handleRequest(ctx context.Context) {
userID := ctx.Value("userID")
if userID != nil {
fmt.Println("用户ID:", userID)
}
}
Value用于传递请求范围数据,不要滥用传递可选参数。
取消传播机制
子Context继承取消
Go
func main() {
parent, cancel := context.WithCancel(context.Background())
// 子Context继承parent的取消
child, _ := context.WithCancel(parent)
go func() {
select {
case <-child.Done():
fmt.Println("子收到取消")
}
}()
cancel() // 取消parent
// child也被取消
}
传播链
Go
Background → WithCancel → WithTimeout → WithValue
↓ ↓
子Context 孙Context
取消parent → 所有子Context收到Done信号
HTTP请求超时
客户端超时
Go
func fetchWithTimeout(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
服务端处理
Go
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // 从请求获取Context
// 客户端断开时ctx自动取消
err := processRequest(ctx)
if err == context.Canceled {
fmt.Println("客户端断开")
}
}
Context使用规则
1. 不要存储Context
Go
// 错误:结构体存储Context
type Service struct {
ctx context.Context // 不要这样做
}
// 正确:函数参数传递
func (s *Service) Do(ctx context.Context) {
}
2. Context作为第一个参数
Go
// 标准函数签名
func Do(ctx context.Context, arg Arg) error
// 而非
func Do(arg Arg, ctx context.Context) error
3. 不要传递nil Context
Go
// 错误
func Do(ctx context.Context) {
if ctx == nil {
ctx = context.Background() // 不推荐
}
}
// 正确:调用者传递有效Context
Do(context.Background())
4. 调用cancel释放资源
text
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel() // 必须调用,即使操作完成
// 原因:cancel释放定时器等资源
Context类型对比
| 创建函数 | 用途 | 取消方式 |
|---|---|---|
| Background | 根Context | 不取消 |
| TODO | 占位 | 不取消 |
| WithCancel | 手动取消 | cancel() |
| WithTimeout | 超时 | 到达时间 |
| WithDeadline | 截止时间 | 到达时间 |
| WithValue | 传递值 | 继承parent |
要点总结
- Background是根Context,永不取消
- WithCancel手动取消,调用cancel()释放资源
- WithTimeout设置超时,自动取消
- WithDeadline设置截止时间点
- WithValue传递请求范围数据
- 取消信号向下传播给所有子Context
- Context作为函数第一个参数
- 不要在结构体中存储Context
- 不要传递nil Context
- defer cancel()确保资源释放
📝 发现内容有误?点击此处直接编辑