Go 语言完全指南 / 16 - Context:取消传播、超时控制、值传递
16 - Context
16.1 Context 概述
context.Context 是 Go 并发编程的核心原语,用于在 goroutine 之间传递取消信号、截止时间和请求范围的值。
type Context interface {
Deadline() (deadline time.Time, ok bool) // 截止时间
Done() <-chan struct{} // 取消信号 channel
Err() error // 取消原因
Value(key any) any // 取值
}
16.2 创建 Context
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 1. Background:根 context,永不取消
ctx := context.Background()
// 2. TODO:标记待处理的 context
ctx2 := context.TODO()
// 3. WithCancel:可手动取消
ctx3, cancel := context.WithCancel(ctx)
defer cancel()
// 4. WithTimeout:超时自动取消
ctx4, cancel4 := context.WithTimeout(ctx, 5*time.Second)
defer cancel4()
// 5. WithDeadline:指定时间取消
deadline := time.Now().Add(10 * time.Second)
ctx5, cancel5 := context.WithDeadline(ctx, deadline)
defer cancel5()
// 6. WithValue:附加键值对
ctx6 := context.WithValue(ctx, "requestID", "abc-123")
fmt.Println(ctx, ctx2, ctx3, ctx4, ctx5, ctx6)
}
| 创建方式 | 说明 | 使用场景 |
|---|---|---|
Background() | 根 context,不取消 | main、init、测试 |
TODO() | 占位符 | 不确定时使用 |
WithCancel() | 手动取消 | 需要主动取消 |
WithTimeout() | 超时取消 | HTTP 请求超时 |
WithDeadline() | 定时取消 | 精确时间控制 |
WithValue() | 传递值 | 请求 ID、认证信息 |
16.3 取消传播
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Printf("[%s] 收到取消信号: %v\n", name, ctx.Err())
return
default:
fmt.Printf("[%s] 工作中...\n", name)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
// 父 context 取消时,所有子 context 也会取消
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx, "worker-1")
go worker(ctx, "worker-2")
// 子 context
childCtx, childCancel := context.WithCancel(ctx)
go worker(childCtx, "child-worker")
time.Sleep(2 * time.Second)
// 取消父 context(所有子 context 也会被取消)
fmt.Println("取消父 context...")
cancel()
// 即使手动调用子 cancel 也不影响父 context
childCancel()
time.Sleep(1 * time.Second)
fmt.Println("完成")
}
16.4 超时控制
package main
import (
"context"
"fmt"
"time"
)
func slowOperation(ctx context.Context) (string, error) {
// 模拟耗时操作
select {
case <-time.After(3 * time.Second):
return "操作完成", nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
// 设置 2 秒超时
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
result, err := slowOperation(ctx)
if err != nil {
fmt.Println("错误:", err) // 错误: context deadline exceeded
} else {
fmt.Println("结果:", result)
}
}
HTTP 请求超时
func fetchURL(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
data, err := fetchURL(ctx, "https://api.example.com/data")
if err != nil {
fmt.Println("请求失败:", err)
return
}
fmt.Println("数据:", string(data))
}
16.5 值传递
package main
import (
"context"
"fmt"
)
// 定义专用类型避免键冲突
type contextKey string
const (
requestIDKey contextKey = "requestID"
userIDKey contextKey = "userID"
)
func WithRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
func GetRequestID(ctx context.Context) string {
if id, ok := ctx.Value(requestIDKey).(string); ok {
return id
}
return ""
}
func WithUserID(ctx context.Context, id int) context.Context {
return context.WithValue(ctx, userIDKey, id)
}
func GetUserID(ctx context.Context) int {
if id, ok := ctx.Value(userIDKey).(int); ok {
return id
}
return 0
}
func handler(ctx context.Context) {
reqID := GetRequestID(ctx)
userID := GetUserID(ctx)
fmt.Printf("处理请求: RequestID=%s, UserID=%d\n", reqID, userID)
}
func main() {
ctx := context.Background()
ctx = WithRequestID(ctx, "req-abc-123")
ctx = WithUserID(ctx, 42)
handler(ctx)
}
⚠️ 注意:Context 值应该只传递请求范围的数据(如请求 ID、认证令牌),不要传递业务参数。
16.6 实际应用模式
服务优雅关闭
func server() {
ctx, cancel := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer cancel()
srv := &http.Server{Addr: ":8080"}
go func() {
<-ctx.Done()
shutdownCtx, shutdownCancel := context.WithTimeout(
context.Background(), 10*time.Second)
defer shutdownCancel()
srv.Shutdown(shutdownCtx)
}()
srv.ListenAndServe()
}
并发请求
func fetchAll(ctx context.Context, urls []string) []Result {
results := make([]Result, len(urls))
var wg sync.WaitGroup
for i, url := range urls {
wg.Add(1)
go func(i int, url string) {
defer wg.Done()
data, err := fetchURL(ctx, url)
results[i] = Result{Data: data, Err: err}
}(i, url)
}
wg.Wait()
return results
}
🏢 业务场景
- HTTP 请求链路:携带请求 ID、超时控制
- 数据库查询:context 传递超时和取消信号
- 微服务调用:传播取消信号到下游服务
- 批量任务:父 context 取消时终止所有子任务
- 优雅关闭:监听系统信号,传播取消