开发者-导航 猿导航

Go 语言中 context 包(AI)

发布时间:

Go 语言的 context 包提供了一种在多个 goroutine 之间传递"上下文"的机制,主要用于控制并发任务的生命周期,包括设置超时时间、取消操作以及传递请求范围的元数据。

核心功能:Deadlines(截止时间)与 Cancellations(取消) #

context 包的核心是 Context 接口,它定义了以下关键方法:

常用的 Context 实现:

常见使用场景(Common Usecases) #

1. 超时控制(Timeout) #

限制操作的执行时间,避免长时间阻塞。

context timeout context_cancel.go
package main

import (
	"context"
	"fmt"
	"time"
)

// 模拟一个可能耗时的操作
func longRunningTask(ctx context.Context) error {
	select {
	case <-time.After(3 * time.Second): // 模拟任务耗时3秒
		fmt.Println("任务完成")
		return nil
	case <-ctx.Done(): // 监听上下文取消信号
		return fmt.Errorf("任务被取消: %v", ctx.Err())
	}
}

func main() {
	// 创建一个2秒后超时的上下文
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel() // 确保资源释放

	fmt.Println("开始执行任务,超时时间2秒")
	err := longRunningTask(ctx)
	if err != nil {
		fmt.Println("错误:", err)
		return
	}
	fmt.Println("程序正常结束")
}

运行结果会显示任务因超时而被取消,因为任务需要3秒而超时设置为2秒。

2. 取消操作(Cancellation) #

手动取消一组相关的 goroutine,例如用户终止请求时。

context cancel context_cancel.go
package main

import (
	"context"
	"fmt"
	"time"
)

// 工作函数,监听上下文取消信号
func worker(ctx context.Context, id int) {
	for {
		select {
		case <-ctx.Done():
			fmt.Printf("工作者 %d 收到取消信号: %v\n", id, ctx.Err())
			return
		default:
			fmt.Printf("工作者 %d 正在工作...\n", id)
			time.Sleep(500 * time.Millisecond)
		}
	}
}

func main() {
	// 创建可取消的上下文
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 确保程序退出时取消

	// 启动3个工作者
	for i := 1; i <= 3; i++ {
		go worker(ctx, i)
	}

	// 运行2秒后手动取消
	time.Sleep(2 * time.Second)
	fmt.Println("主程序:开始取消所有工作者")
	cancel()

	// 等待所有工作者退出
	time.Sleep(1 * time.Second)
	fmt.Println("主程序:所有工作者已退出")
}

执行后会看到所有工作者在收到取消信号后停止工作。

3. 传递请求范围的元数据 #

在处理请求的整个调用链中传递数据(如请求ID、用户认证信息等)。

context value context_value.go
package main

import (
	"context"
	"fmt"
)

// 定义类型化的key,避免键冲突
type contextKey string
const RequestIDKey contextKey = "requestID"

// 模拟处理链中的函数
func handleRequest(ctx context.Context) {
	process1(ctx)
}

func process1(ctx context.Context) {
	process2(ctx)
}

func process2(ctx context.Context) {
	// 从上下文中获取请求ID
	if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
		fmt.Printf("处理请求,ID: %s\n", reqID)
	} else {
		fmt.Println("未找到请求ID")
	}
}

func main() {
	// 创建携带请求ID的上下文
	ctx := context.WithValue(context.Background(), RequestIDKey, "REQ-12345")
	
	// 处理请求
	handleRequest(ctx)
}
    

最佳实践 #

  1. 不要将 Context 存储在结构体中,应作为函数参数传递
  2. 优先使用 context.Background() 作为根上下文
  3. Context 是线程安全的,可以被多个 goroutine 同时使用
  4. 避免使用 WithValue 传递大量数据,它主要用于请求范围的元数据
  5. 及时调用 cancel 函数释放资源,通常使用 defer 确保

context 包是 Go 并发编程中控制 goroutine 生命周期的重要工具,尤其在分布式系统、服务端编程中广泛用于处理超时、取消和请求跟踪。