Context
Context 是 Go 1.7 引入的标准库,用于在 Goroutine 之间传递请求范围的数据、取消信号和超时。
Context 基础
context 包
go
import "context"Context 接口
go
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}创建 Context
根 Context
go
// 创建根 Context
ctx := context.Background()
// 或者使用 TODO(用于不确定的场景)
ctx := context.TODO()带取消的 Context
go
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保取消
// 在另一个 Goroutine 中取消
go func() {
time.Sleep(2 * time.Second)
cancel()
}()
// 等待取消
<-ctx.Done()
fmt.Println("Context 已取消")带超时的 Context
go
// 超时 Context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 使用
select {
case <-ctx.Done():
fmt.Println("超时")
case <-time.After(10 * time.Second):
fmt.Println("完成")
}带截止时间的 Context
go
deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()带值的 Context
go
ctx := context.WithValue(context.Background(), "userID", 123)
userID := ctx.Value("userID")Context 使用场景
1. 请求取消
go
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker 被取消")
return
default:
// 执行工作
fmt.Println("工作中...")
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
time.Sleep(2 * time.Second)
cancel() // 取消 Worker
time.Sleep(1 * time.Second)
}2. 超时控制
go
func fetchData(ctx context.Context) (string, error) {
// 模拟网络请求
select {
case <-time.After(3 * time.Second):
return "数据", nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
data, err := fetchData(ctx)
if err != nil {
fmt.Println("错误:", err) // context deadline exceeded
return
}
fmt.Println("数据:", data)
}3. 值传递
go
type key string
const userIDKey key = "userID"
const userNameKey key = "userName"
func setUser(ctx context.Context, id int, name string) context.Context {
ctx = context.WithValue(ctx, userIDKey, id)
ctx = context.WithValue(ctx, userNameKey, name)
return ctx
}
func getUser(ctx context.Context) (int, string) {
id := ctx.Value(userIDKey).(int)
name := ctx.Value(userNameKey).(string)
return id, name
}
func main() {
ctx := context.Background()
ctx = setUser(ctx, 123, "Alice")
id, name := getUser(ctx)
fmt.Printf("用户 ID: %d, 用户名: %s\n", id, name)
}Context 传播
在函数间传递
go
func handler(ctx context.Context) {
// 传递 Context 给其他函数
processRequest(ctx)
}
func processRequest(ctx context.Context) {
// 检查 Context 是否已取消
if ctx.Err() != nil {
return
}
// 继续处理
doWork(ctx)
}在 HTTP 请求中使用
go
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// 创建带超时的 Context
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// 传递给业务逻辑
result, err := businessLogic(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write([]byte(result))
}Context 最佳实践
1. Context 作为第一个参数
go
// 好的做法
func doSomething(ctx context.Context, param string) error {
// ...
}
// 不好的做法
func doSomething(param string, ctx context.Context) error {
// ...
}2. 不要存储 Context
go
// 不好的做法
type Server struct {
ctx context.Context
}
// 好的做法:每次从参数传递
func (s *Server) Handle(ctx context.Context, req *Request) {
// ...
}3. 使用类型安全的键
go
// 不好的做法
ctx := context.WithValue(ctx, "userID", 123)
// 好的做法
type key string
const userIDKey key = "userID"
ctx := context.WithValue(ctx, userIDKey, 123)4. 检查 Context 状态
go
func process(ctx context.Context) error {
// 检查是否已取消
if ctx.Err() != nil {
return ctx.Err()
}
// 继续处理
return nil
}完整示例
go
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d 被取消: %v\n", id, ctx.Err())
return
default:
fmt.Printf("Worker %d 工作中...\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}
func fetchData(ctx context.Context, url string) (string, error) {
// 模拟网络请求
select {
case <-time.After(2 * time.Second):
return fmt.Sprintf("数据来自 %s", url), nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
// 示例 1: 取消
ctx1, cancel1 := context.WithCancel(context.Background())
go worker(ctx1, 1)
time.Sleep(2 * time.Second)
cancel1()
time.Sleep(500 * time.Millisecond)
// 示例 2: 超时
ctx2, cancel2 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel2()
data, err := fetchData(ctx2, "https://example.com")
if err != nil {
fmt.Printf("获取数据失败: %v\n", err)
} else {
fmt.Println("数据:", data)
}
// 示例 3: 值传递
type key string
const userIDKey key = "userID"
ctx3 := context.WithValue(context.Background(), userIDKey, 123)
userID := ctx3.Value(userIDKey)
fmt.Printf("用户 ID: %v\n", userID)
}常见模式
链式 Context
go
func middleware1(ctx context.Context) context.Context {
return context.WithValue(ctx, "key1", "value1")
}
func middleware2(ctx context.Context) context.Context {
return context.WithValue(ctx, "key2", "value2")
}
func handler(ctx context.Context) {
ctx = middleware1(ctx)
ctx = middleware2(ctx)
// 使用 ctx
}Context 树
go
parentCtx := context.Background()
// 创建子 Context
childCtx1, cancel1 := context.WithCancel(parentCtx)
childCtx2, cancel2 := context.WithTimeout(parentCtx, 5*time.Second)
// 取消父 Context 会影响所有子 Context
// 但取消子 Context 不会影响父 Context下一步
接下来我们将学习:
