当前位置: 首页 > article >正文

Go中的context 包使用详解

context 包在 Go 中非常重要,特别是在处理并发和超时的场景下,它能让你在多个 goroutine 之间传递取消信号、超时控制或其他控制信息。context 是 Go 并发模型中的一个重要工具,尤其适用于 HTTP 请求、数据库操作、分布式系统等场景。

1. 基本概念

context 包的主要功能是提供一个 Context 类型,允许在多个 goroutine 之间共享请求范围的信息,并在需要时提供取消信号。

Context 是不可变的,一旦创建就不能修改。每次操作都返回一个新的 Context 对象。

2. 常见用法

  • 传递取消信号:在多个 goroutine 之间传递取消信号。
  • 设置超时:为操作设置超时限制。
  • 传递请求范围的值:可以在上下文中传递请求的元数据(如用户 ID、请求 ID)。

3. context 包的基本结构

context 包提供了几种不同类型的 Context,最常用的包括:

  • context.Background():根上下文,通常用于程序的顶层。
  • context.TODO():用于暂时没有明确上下文的地方,表示一个不确定的上下文。
  • context.WithCancel():创建一个可取消的上下文。
  • context.WithTimeout():创建一个带有超时的上下文。
  • context.WithDeadline():创建一个带有具体截止时间的上下文。
  • context.WithValue():创建一个可以存储值的上下文。

4. context.Background()context.TODO()

  • context.Background() 通常用于程序的顶层调用,表示一个没有附加元数据的空上下文。它通常作为其他上下文的根基。
  • context.TODO() 用于你不确定当前使用哪种上下文的地方,表示暂时还没有决定。
ctx := context.Background()  // 用作根上下文
// 或者
ctx := context.TODO()  // 用作还未确定上下文的地方

5. context.WithCancel()

context.WithCancel() 创建一个可以手动取消的上下文。当你取消这个上下文时,所有基于该上下文的 goroutine 都会收到取消信号。

package main

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

func doWork(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("Work cancelled:", ctx.Err())
			return
		default:
			// 模拟一些工作
			fmt.Println("Working...")
			time.Sleep(1 * time.Second)
		}
	}
}

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

	// 启动一个 goroutine 执行任务
	go doWork(ctx)

	// 模拟执行一段时间后取消
	time.Sleep(3 * time.Second)
	cancel()  // 取消上下文,通知 goroutine 停止工作

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

输出:

Working...
Working...
Working...
Work cancelled: context canceled

在这个例子中,cancel() 触发了 ctx.Done() 的信号,通知 doWork 函数退出。

6. context.WithTimeout()context.WithDeadline()

context.WithTimeout() 创建一个具有超时限制的上下文,超时后自动取消。context.WithDeadline() 类似,但你指定的是一个具体的截止时间。

示例:使用 WithTimeout
package main

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

func doWork(ctx context.Context) {
	select {
	case <-time.After(2 * time.Second):  // 模拟耗时操作
		fmt.Println("Work completed")
	case <-ctx.Done():
		fmt.Println("Work cancelled:", ctx.Err())
	}
}

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

	// 启动 goroutine 执行任务
	go doWork(ctx)

	// 等待 2 秒,超过了超时时间
	time.Sleep(2 * time.Second)
}

输出:

Work cancelled: context deadline exceeded

在这个例子中,由于上下文在 1 秒后超时,doWork 会在超时后取消任务。

示例:使用 WithDeadline
package main

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

func doWork(ctx context.Context) {
	select {
	case <-time.After(2 * time.Second):  // 模拟耗时操作
		fmt.Println("Work completed")
	case <-ctx.Done():
		fmt.Println("Work cancelled:", ctx.Err())
	}
}

func main() {
	// 创建一个截止时间为当前时间加 1 秒的上下文
	deadline := time.Now().Add(1 * time.Second)
	ctx, cancel := context.WithDeadline(context.Background(), deadline)
	defer cancel()

	// 启动 goroutine 执行任务
	go doWork(ctx)

	// 等待 2 秒,超出了截止时间
	time.Sleep(2 * time.Second)
}

输出:

Work cancelled: context deadline exceeded

7. context.WithValue()

context.WithValue() 允许你在上下文中存储键值对,以便在不同的 goroutine 之间共享数据。通常用于请求 ID 或其他元数据的传递。

package main

import (
	"context"
	"fmt"
)

type key string

func main() {
	// 创建一个上下文并在其中存储一个值
	ctx := context.WithValue(context.Background(), key("userID"), 42)

	// 从上下文中获取值
	userID := ctx.Value(key("userID"))
	fmt.Println("UserID:", userID)
}

输出:

UserID: 42

8. context 在并发中的典型应用场景

  • HTTP 请求处理:在 web 应用中,你通常会为每个请求创建一个 context,以便在请求生命周期内传递取消信号、超时、身份验证信息等。
  • 数据库操作:当进行数据库查询时,可以使用 context 来设置超时、取消信号等,确保操作不会因超时而挂起。
  • 分布式任务:在微服务架构中,context 可以在服务之间传递请求信息,并确保某个操作超时或取消时其他相关操作也会停止。

9. context 的使用注意事项

  • 不要将 context 存储在结构体或 global 变量中,因为 context 是短生命周期的,应该只在请求的生命周期内传递。
  • 不要将 context 用作存储实际的数据,它主要用于传递控制信号(如取消信号、超时等)。
  • 避免重复创建:在每次使用 context.WithXXX 时,应该返回一个新的 Context,并通过 defer cancel() 确保在操作完成后释放资源。

总结

  • context 是 Go 中非常重要的并发工具,适用于传递取消信号、超时控制和请求范围的数据。
  • 主要的创建函数包括 WithCancelWithTimeoutWithDeadlineWithValue,它们允许在不同的场景下创建上下文。
  • context 的传递机制让我们能够在多个 goroutine 之间共享控制信息,如取消信号、超时或元数据,避免了手动的锁机制,使并发编程更加简洁和高效。

http://www.kler.cn/a/473400.html

相关文章:

  • Sql 创建用户
  • 自创“九转化形”算法设计,禁止抄袭
  • 通过 route 或 ip route 管理Linux主机路由
  • Flink源码解析之:Flink on k8s 客户端提交任务源码分析
  • mysql中查询json的技巧
  • spring boot 集成 knife4j
  • 图像处理|膨胀操作
  • Windows系统安装Docker Desktop
  • 【踩坑】SparkSQL union/unionAll 函数的去重问题
  • JavaScript动态渲染页面爬取之Selenium
  • Elasticsearch:在 HNSW 中提前终止以实现更快的近似 KNN 搜索
  • 实习总结(经历篇)
  • 聚类系列 (二)——HDBSCAN算法详解
  • GO:sync.Map
  • springboot远程链接spark
  • 力扣 34. 在排序数组中查找元素的第一个和最后一个位置
  • SpringBoot3动态切换数据源
  • 基于springboot的网上商城购物系统
  • 2025.1.8(c++对c语言的扩充——堆区空间,引用,函数)
  • Mysql面试相关
  • 使用 vue3 赋值后视图没变化的问题
  • 蓝桥杯训练
  • T-SQL语言的语法
  • 使用 SQLite3 的基本操作步骤
  • Azkaban其一,介绍、体系架构和安装
  • Linux-----结构体与联合体,大小端模式