云原生探索系列(十九):Go 语言 context.Context

前言

在 Go 语言中, context.Context 是一个重要的工具,广泛应用于处理并发操作、取消信号、超时控制等场景。 无论是网络请求、数据库操作,还是长时间运行的任务, context.Context 都能提供灵活的控制机制,帮助开发者以一种结构化的方式管理这些操作的生命周期。

context.Context

context.Context 是 Go 语言中处理请求范围内的元数据、取消信号和超时控制的标准方式。它主要由 Go 标准库中的 context 包提供。

主要作用

  • 取消信号 :在多个 goroutine 之间传递取消信号,可以让某个操作提前中止。
  • 超时控制 :为长时间运行的任务设置超时,防止任务无限期地执行。
  • 请求范围的元数据 :在请求上下文中传递特定的键值对数据,常用于传递认证信息、用户ID等。

主要方法

  • Done() :返回一个 chan struct{} ,当 context 被取消或超时后,这个 channel 会关闭。
  • Err() :返回一个错误,指示上下文被取消的原因。
  • Value() :返回与 context 关联的键值对数据,常用于传递请求级别的元数据。

主要实现

Go 标准库提供了以下几种常见的 context.Context 实现:

  • context.Background() :返回一个空的根上下文,通常在程序的最外层使用,表示没有父上下文。
  • context.TODO() :表示一个尚未决定的上下文,用于未来将要实现的功能。
  • context.WithCancel() :返回一个可取消的上下文。
  • context.WithDeadline() :返回一个具有截止时间的上下文。
  • context.WithTimeout() :返回一个具有超时的上下文。
  • context.WithValue() :返回一个带有键值对的上下文,用于传递元数据。

常见场景

取消操作

通过 context.WithCancel 创建一个可以取消的上下文,并在多个 goroutine 中传递这个上下文,实现任务的协同取消。

go 复制代码
package main

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

func main() {
	// 创建一个可取消的上下文
	ctx, cancel := context.WithCancel(context.Background())

	// 启动两个 goroutine
	go task(ctx, 1)
	go task(ctx, 2)

	time.Sleep(2 * time.Second)
	cancel()

	// 等待 goroutine 完成
	time.Sleep(1 * time.Second)
}

func task(ctx context.Context, id int) {
	for {
		select {
		case <-ctx.Done():
			// 接收到取消信号,结束任务
			fmt.Printf("Task %d canceled\n", id)
			return
		default:
			// 执行任务
			fmt.Printf("Task %d is running\n", id)
			time.Sleep(500 * time.Millisecond)
		}
	}
}

这段代码中, cancel() 被调用时,会向所有使用该上下文的 goroutine 发送取消信号,导致两个 goroutine 都退出。

执行这段代码,输出结果如下:

arduino 复制代码
Task 2 is running
Task 1 is running
Task 1 is running
Task 2 is running
Task 2 is running
Task 1 is running
Task 1 is running
Task 2 is running
Task 2 canceled
Task 1 canceled

超时控制

有时我们需要限制操作的执行时间,防止某个任务超时。 context.WithTimeout 可以帮助我们为操作设置超时限制。

go 复制代码
package main

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

func main() {
	// 创建一个具有超时的上下文,设置超时为 2 秒
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	// 执行任务
	err := doTaskWithTimeout(ctx)
	if err != nil {
		fmt.Println("Error:", err)
	}
}

func doTaskWithTimeout(ctx context.Context) error {
	select {
	case <-time.After(3 * time.Second):
		fmt.Println("Task completed")
		return nil
	case <-ctx.Done():
		// 超时或取消
		return ctx.Err()
	}
}

这段代码中,任务的执行时间是 3 秒,但我们设置了 2 秒的超时时间,因此任务超时并触发了 context deadline exceeded 错误。

这段代码执行后会输出如下内容:

javascript 复制代码
Error: context deadline exceeded

传递请求元数据

使用 context.WithValue 传递元数据

go 复制代码
package main

import (
	"context"
	"fmt"
)

type key string

const userKey key = "user"

func main() {
	// 创建上下文并传递元数据
	ctx := context.WithValue(context.Background(), userKey, "john_doe")

	// 模拟处理请求
	handleRequest(ctx)
}

func handleRequest(ctx context.Context) {
	// 从上下文中获取用户信息
	user := ctx.Value(userKey).(string)
	fmt.Printf("Request handled for user: %s\n", user)
}

这段代码中,我们使用 context.WithValue 将用户信息传递到上下文中,在后续的请求处理中通过 ctx.Value 获取并使用这些信息。

这段代码执行后输出如下内容:

rust 复制代码
Request handled for user: john_doe

最后

在实际开发中, context.Context 的应用场景非常广泛,从 Web 请求处理到数据库查询,再到并发任务的取消与超时控制,都是它的典型用例。

相关推荐
+VX:Fegn08955 分钟前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Victor35620 分钟前
Hibernate(43)Hibernate中的级联删除如何实现?
后端
Victor35623 分钟前
Hibernate(44)Hibernate中的fetch join是什么?
后端
用户30750093037932 小时前
go Eino使用ADK开发agent
后端
原神启动12 小时前
K8S(九)—— Kubernetes 集群调度全面解析
云原生·容器·kubernetes
唐叔在学习2 小时前
Python自动化指令进阶:UAC提权
后端·python
Assby2 小时前
Windows 在 PostgreSQL 上安装 vector 扩展
后端
12344522 小时前
【面试复盘】有了equals为什么还要hashcode
java·后端
小周在成长2 小时前
MyBatis 分页插件PageHelper
后端
Paladin_z2 小时前
Easy Query中间件的使用
后端