Go 语言标准库 —— context 包(上下文)
🔹 概述
context 包定义了 Context 类型,用于在 goroutine 之间传递截止时间、取消信号和其他请求范围的值。
主要功能:
- 取消操作(Cancellation)
- 超时控制(Timeout)
- 截止时间(Deadline)
- 传递请求范围的值(Values)
重要说明:
- context 是并发安全的
- 用于管理 goroutine 的生命周期
- 避免 goroutine 泄漏
- 控制请求的生命周期
- 是 Go 并发编程的核心组件
使用场景:
- HTTP 服务器处理请求
- RPC 调用
- 数据库查询
- 并发任务管理
- 资源清理
🔹 核心接口
Context 接口
context.Context interface
-
说明:
- 上下文接口,所有 context 实现都实现此接口
- 并发安全
- 可以在多个 goroutine 之间共享
-
接口定义:
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } -
方法详解:
-
Deadline()
- 说明:返回截止时间
- 返回值:
deadline time.Time- 截止时间ok bool- 是否设置了截止时间
- 示例:
deadline, ok := ctx.Deadline() if ok { fmt.Println("截止时间:", deadline) }
-
Done()
- 说明:返回一个只读通道
- 返回值:
<-chan struct{}- 当 context 被取消或超时时关闭
- 注意:
- 通道关闭表示 context 完成
- 不应直接读写该通道
- 示例:
select { case <-ctx.Done(): fmt.Println("context 已取消") default: // 继续工作 }
-
Err()
- 说明:返回 context 完成的原因
- 返回值:
error- 如果 Done 未关闭返回 nil- 如果已关闭,返回取消原因
- 错误类型:
Canceled- context 被取消DeadlineExceeded- 超过截止时间
- 示例:
err := ctx.Err() if err == context.Canceled { fmt.Println("已取消") } else if err == context.DeadlineExceeded { fmt.Println("已超时") }
-
Value()
- 说明:获取与 key 关联的值
- 参数:
key interface{}- 键
- 返回值:
interface{}- 与 key 关联的值
- 注意:
- 仅用于传递请求范围的数据
- 不应用于传递配置或选项
- 示例:
type contextKey string const userIDKey contextKey = "userID" value := ctx.Value(userIDKey) if value != nil { userID := value.(string) }
-
🔹 核心函数
创建可取消的 Context
context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)
-
说明:
- 从父 context 创建一个新的可取消的 context
- 返回 context 和取消函数
- 调用 cancel 函数会关闭 ctx.Done() 通道
-
参数:
parent Context- 父 context
-
返回值:
ctx Context- 新的 contextcancel CancelFunc- 取消函数
-
重要说明:
- 必须调用 cancel 函数释放资源
- 通常使用 defer 调用
- 多次调用 cancel 是安全的
-
示例:
package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 确保释放资源 // 启动 goroutine go func() { for i := 0; i < 5; i++ { select { case <-ctx.Done(): fmt.Println("收到取消信号") return default: fmt.Printf("工作 %d\n", i) time.Sleep(1 * time.Second) } } }() // 2 秒后取消 time.Sleep(2 * time.Second) cancel() time.Sleep(1 * time.Second) }
创建带截止时间的 Context
context.WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
-
说明:
- 创建在指定时间自动取消的 context
- 到达时间 d 时自动调用 cancel
-
参数:
parent Context- 父 contextd time.Time- 截止时间
-
返回值:
Context- 新的 contextCancelFunc- 取消函数
-
注意:
- 到达截止时间自动取消
- 也应调用 cancel 释放资源
-
示例:
package main import ( "context" "fmt" "time" ) func main() { // 10 秒后自动取消 ctx, cancel := context.WithDeadline( context.Background(), time.Now().Add(10*time.Second), ) defer cancel() select { case <-time.After(5 * time.Second): fmt.Println("完成工作") case <-ctx.Done(): fmt.Println("context 取消:", ctx.Err()) } }
创建带超时的 Context
context.WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
-
说明:
- 创建在指定超时时间后自动取消的 context
- 等价于 WithDeadline(parent, time.Now().Add(timeout))
-
参数:
parent Context- 父 contexttimeout time.Duration- 超时时长
-
返回值:
Context- 新的 contextCancelFunc- 取消函数
-
注意:
- 超时后自动取消
- 必须调用 cancel 释放资源
-
示例:
package main import ( "context" "fmt" "time" ) func main() { // 5 秒超时 ctx, cancel := context.WithTimeout( context.Background(), 5*time.Second, ) defer cancel() select { case <-time.After(3 * time.Second): fmt.Println("完成工作") case <-ctx.Done(): fmt.Println("超时:", ctx.Err()) } }
创建带值的 Context
context.WithValue(parent Context, key, val interface{}) Context
-
说明:
- 创建携带键值对的 context
- 用于传递请求范围的数据
-
参数:
parent Context- 父 contextkey interface{}- 键(建议使用自定义类型)val interface{}- 值
-
返回值:
Context- 新的 context
-
重要说明:
- 键应该使用自定义类型(避免冲突)
- 不应用于传递配置
- 不应用于传递大量数据
-
示例:
package main import ( "context" "fmt" ) // 使用自定义类型作为 key type contextKey string const ( userIDKey contextKey = "userID" usernameKey contextKey = "username" ) func main() { ctx := context.Background() // 添加值 ctx = context.WithValue(ctx, userIDKey, "123") ctx = context.WithValue(ctx, usernameKey, "alice") // 获取值 userID := ctx.Value(userIDKey).(string) username := ctx.Value(usernameKey).(string) fmt.Printf("User: %s (%s)\n", username, userID) }
🔹 预定义 Context
背景 Context
context.Background()
-
说明:
- 返回一个空的 context
- 永不取消,没有值,没有截止时间
- 通常作为根 context 使用
-
返回值:
Context- 背景 context
-
使用场景:
- main 函数
- 测试
- 作为其他 context 的父 context
-
示例:
// 作为根 context ctx := context.Background() // 创建子 context ctx, cancel := context.WithCancel(ctx) defer cancel()
待办 Context
context.TODO()
-
说明:
- 返回一个空的 context
- 当不确定使用哪个 context 时使用
- 代码审查时会被标记
-
返回值:
Context- 待办 context
-
使用场景:
- 重构时临时使用
- 不确定使用哪个 context 时
-
示例:
// 临时使用,稍后替换 ctx := context.TODO() // 代码审查时会提醒替换为合适的 context
🔹 使用场景
1. HTTP 服务器中的取消
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func handler(w http.ResponseWriter, r *http.Request) {
// 从请求中获取 context
ctx := r.Context()
// 模拟长时间运行的任务
for i := 0; i < 10; i++ {
select {
case <-ctx.Done():
fmt.Println("请求已取消")
return
default:
fmt.Printf("处理请求 %d\n", i)
time.Sleep(500 * time.Millisecond)
}
}
w.Write([]byte("请求完成"))
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", nil)
}
2. 带超时的数据库查询
package main
import (
"context"
"database/sql"
"fmt"
"time"
_ "github.com/lib/pq"
)
func queryWithTimeout(db *sql.DB, query string, timeout time.Duration) error {
// 创建带超时的 context
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// 使用 context 执行查询
rows, err := db.QueryContext(ctx, query)
if err != nil {
return fmt.Errorf("查询失败:%w", err)
}
defer rows.Close()
// 处理结果
for rows.Next() {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 处理行数据
}
}
return rows.Err()
}
func main() {
db, _ := sql.Open("postgres", "conn_string")
err := queryWithTimeout(db, "SELECT * FROM users", 5*time.Second)
if err != nil {
fmt.Println("查询错误:", err)
}
}
3. 并发任务管理
package main
import (
"context"
"fmt"
"sync"
"time"
)
// Worker 执行任务
func Worker(ctx context.Context, id int, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d 收到取消信号\n", id)
return
default:
fmt.Printf("Worker %d 工作中...\n", id)
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg sync.WaitGroup
// 启动 5 个 worker
for i := 1; i <= 5; i++ {
wg.Add(1)
go Worker(ctx, i, &wg)
}
// 运行 3 秒后取消
time.Sleep(3 * time.Second)
fmt.Println("取消所有任务")
cancel()
// 等待所有 worker 完成
wg.Wait()
fmt.Println("所有任务完成")
}
4. 多级取消(父子 Context)
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 创建根 context
rootCtx, rootCancel := context.WithCancel(context.Background())
defer rootCancel()
// 创建子 context
childCtx, childCancel := context.WithCancel(rootCtx)
defer childCancel()
// 启动 goroutine 监听根 context
go func() {
<-rootCtx.Done()
fmt.Println("根 context 取消")
}()
// 启动 goroutine 监听子 context
go func() {
<-childCtx.Done()
fmt.Println("子 context 取消")
}()
// 取消子 context
time.Sleep(1 * time.Second)
childCancel()
time.Sleep(1 * time.Second)
// 取消根 context(会传播到所有子 context)
rootCancel()
time.Sleep(1 * time.Second)
}
5. 管道和 context 组合
package main
import (
"context"
"fmt"
"time"
)
// 生成器
func generator(ctx context.Context, out chan<- int) {
defer close(out)
i := 0
for {
select {
case <-ctx.Done():
fmt.Println("生成器收到取消信号")
return
case out <- i:
i++
time.Sleep(100 * time.Millisecond)
}
}
}
// 处理器
func processor(ctx context.Context, in <-chan int) {
for {
select {
case <-ctx.Done():
fmt.Println("处理器收到取消信号")
return
case v, ok := <-in:
if !ok {
return
}
fmt.Printf("处理:%d\n", v)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
ch := make(chan int)
// 启动生成器
go generator(ctx, ch)
// 启动处理器
go processor(ctx, ch)
// 等待完成
<-ctx.Done()
time.Sleep(500 * time.Millisecond)
}
6. 重试机制
package main
import (
"context"
"fmt"
"time"
)
// 带重试的操作
func doWithRetry(ctx context.Context, operation func(context.Context) error, maxRetries int) error {
var lastErr error
for i := 0; i < maxRetries; i++ {
// 创建带超时的子 context
retryCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
// 执行操作
lastErr = operation(retryCtx)
cancel()
if lastErr == nil {
return nil // 成功
}
// 检查是否应该放弃
if ctx.Err() != nil {
return ctx.Err()
}
fmt.Printf("重试 %d/%d: %v\n", i+1, maxRetries, lastErr)
time.Sleep(time.Duration(i+1) * time.Second)
}
return lastErr
}
func main() {
ctx := context.Background()
// 模拟可能失败的操作
operation := func(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 模拟随机失败
if time.Now().Unix()%2 == 0 {
return fmt.Errorf("操作失败")
}
fmt.Println("操作成功")
return nil
}
}
err := doWithRetry(ctx, operation, 3)
if err != nil {
fmt.Println("最终失败:", err)
}
}
🔹 注意事项和最佳实践
1. 必须调用 Cancel 函数
- ⚠️ 重要:忘记调用 cancel 会导致资源泄漏
- ✅ 始终使用 defer 调用 cancel
// 正确
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 错误 - 会导致资源泄漏
ctx, cancel := context.WithCancel(context.Background())
// 忘记调用 cancel()
2. 不要存储 Context 到结构体
- ⚠️ Context 应该作为函数的第一个参数传递
- ❌ 不要将 Context 存储在结构体字段中
// 错误
type Service struct {
ctx context.Context // 不应该这样
}
// 正确
type Service struct{}
func (s *Service) Run(ctx context.Context) {
// 使用 ctx
}
3. 使用自定义类型作为 Value 的 Key
- ✅ 使用自定义类型避免冲突
- ⚠️ 不要使用 string 作为 key
// 错误 - 可能冲突
ctx = context.WithValue(ctx, "userID", "123")
// 正确 - 使用自定义类型
type contextKey string
const userIDKey contextKey = "userID"
ctx = context.WithValue(ctx, userIDKey, "123")
4. Value 仅用于请求范围的数据
- ✅ 传递认证信息、请求 ID 等
- ❌ 不要传递配置或选项
- ❌ 不要传递大量数据
// 正确 - 传递请求范围的数据
ctx = context.WithValue(ctx, requestIDKey, reqID)
ctx = context.WithValue(ctx, authTokenKey, token)
// 错误 - 不应该传递配置
ctx = context.WithValue(ctx, "config", config)
5. 传播取消信号
- ✅ 子 context 会继承父 context 的取消信号
- ✅ 取消父 context 会取消所有子 context
// 创建父子 context
parent, cancel := context.WithCancel(context.Background())
child, childCancel := context.WithCancel(parent)
// 取消 parent 会同时取消 child
cancel()
6. 并发安全
- ✅ Context 是并发安全的
- ✅ 可以在多个 goroutine 之间共享
ctx, cancel := context.WithCancel(context.Background())
// 多个 goroutine 可以安全使用
go worker(ctx)
go worker(ctx)
go worker(ctx)
🔹 Context 树结构
父子关系
context.Background() (根)
├── WithCancel
│ ├── WithTimeout
│ └── WithValue
├── WithTimeout
└── WithValue
取消传播
取消父 context
↓
所有子 context 都会被取消
↓
所有监听 Done 通道的 goroutine 都会收到信号
🔥 总结
核心接口
| 接口 | 说明 |
|---|---|
| Context | 上下文接口(4 个方法) |
Context 方法
| 方法 | 说明 |
|---|---|
| Deadline() | 返回截止时间 |
| Done() | 返回取消信号通道 |
| Err() | 返回取消原因 |
| Value() | 获取关联的值 |
核心函数
| 函数 | 说明 | 用途 |
|---|---|---|
| WithCancel(parent) | 创建可取消的 context | 手动取消 |
| WithDeadline(parent, d) | 创建带截止时间的 context | 定时取消 |
| WithTimeout(parent, d) | 创建带超时的 context | 超时控制 |
| WithValue(parent, k, v) | 创建带值的 context | 传递数据 |
预定义 Context
| Context | 说明 | 使用场景 |
|---|---|---|
| Background() | 空 context,永不取消 | 根 context |
| TODO() | 临时 context | 重构时使用 |
主要特点
- 并发安全 👉 可在多个 goroutine 间共享
- 取消传播 👉 父 context 取消会传播到子 context
- 资源管理 👉 必须调用 cancel 释放资源
- 轻量级 👉 开销很小
使用场景
- HTTP 服务器 👉 请求处理取消
- 数据库查询 👉 超时控制
- 并发任务 👉 任务管理
- RPC 调用 👉 超时和取消
- 管道处理 👉 流式数据处理
最佳实践
- ✅ 将 Context 作为函数的第一个参数
- ✅ 始终调用 cancel 函数(使用 defer)
- ✅ 使用自定义类型作为 Value 的 key
- ✅ 仅传递请求范围的数据
- ✅ 不要存储 Context 到结构体
- ⚠️ 注意:Value 不应传递配置
错误处理
// 检查取消原因
select {
case <-ctx.Done():
if ctx.Err() == context.Canceled {
fmt.Println("已取消")
} else if ctx.Err() == context.DeadlineExceeded {
fmt.Println("已超时")
}
}
常见错误
- ❌ 忘记调用 cancel 函数
- ❌ 将 Context 存储到结构体
- ❌ 使用 string 作为 Value 的 key
- ❌ 滥用 Value 传递配置
- ❌ 不传播 Context
context 包是 Go 并发编程的核心组件,用于管理 goroutine 的生命周期和传递请求范围的数据!