【GO】Context

embedded/2025/1/26 15:22:22/

context包的核心目的是为了帮助开发者编写更加高效、可靠和可维护的并发程序。它提供了一套工具来处理请求范围的数据传递、超时、取消信号和goroutine生命周期管理等问题,这些都是现代分布式系统开发中的关键挑战。通过合理使用context,可以显著改善应用程序的行为,并确保其在面对复杂的工作负载时仍然保持良好的性能和稳定性。

1. context.Context 接口

context.Context 是一个接口,定义了四个方法:

Deadline() (deadline time.Time, ok bool):返回上下文截止时间(如果有的话)。如果没有设置,则okfalse

Done() <-chan struct{}:返回一个通道,当该上下文被取消或超时时会关闭这个通道。可以监听这个通道来了解何时应该停止工作。

Err() error:在Done()通道关闭后调用,以获取导致上下文结束的原因(如上下文被取消或超时)。

Value(key interface{}) interface{}:用于从上下文中检索键值对中的值。只有当你需要传递请求特定的数据时才使用它。

2. 创建Context

context.Background() 和 context.TODO() 都是创建一个新的空上下文的方法,但它们有不同用途:

Background() 主要用于主函数、初始化和测试代码中,作为所有其他上下文的根。

TODO() 用来表示开发者还没有决定如何处理上下文,通常不应该出现在生产代码中。

3. 派生新Context

派生新的Context意味着基于现有的Context创建一个新的子Context。这允许你添加额外的信息到现有Context上,比如超时或取消信号。以下是几个重要的函数:

WithCancel(parent Context): 返回一个新的Context和一个取消函数。当调用取消函数时,新的Context会被取消,并且任何等待Done()通道的操作都会立即收到通知。

WithDeadline(parent Context, deadline time.Time): 类似于WithCancel,但它会在指定的时间点自动取消新的Context。

WithTimeout(parent Context, timeout time.Duration): 设置一个相对的时间期限,在这段时间之后自动取消新的Context。

WithValue(parent Context, key, val interface{}): 允许你在Context中存储键值对。注意,为了类型安全,通常会定义一个特定类型的key类型(例如,自定义的结构体类型),并且只将它与特定的Context一起使用。

4. 取消Context

当你不再需要一个Context或者它的衍生Context时,你应该尽快调用取消函数。这样做有助于及时释放资源,并确保你的程序不会浪费计算能力在已经不需要的任务上。

5. 使用Context

在一个长时间运行的操作中,你可以定期检查ctx.Done()是否关闭,以此来决定是否应该提前退出操作。如果你正在接收数据,那么在接收之前检查ctx.Err()是否为非nil可以帮助你避免不必要的工作。

6. Context不是为了传递业务数据

尽管WithValue方法允许你向Context添加值,但这不应该成为传递业务逻辑数据的主要方式。Context应该只包含少量的元数据,如请求ID、认证信息等,而不应该包含复杂的业务状态。

7. Context不应该被存储在结构体中

由于Context是设计为随着函数调用链向下传递的,因此不应该将其保存为结构体的字段。这样做的原因是为了避免潜在的生命周期问题以及使代码更加清晰和易于理解。

8. 避免使用全局变量保存Context

使用全局变量保存Context会导致程序难以理解和维护,因为这使得跟踪Context的来源和使用变得困难。相反,应当通过参数列表显式地传递Context。

9.场景使用

创建基础Context

context.Background()
Go">ctx := context.Background()
  • 通常用于主函数、初始化代码或测试代码,作为所有其他Context的根。
  • 是一个永远不会被取消的空Context。
context.TODO()
Go">ctx := context.TODO()
  • 当开发者还没有决定如何处理上下文时使用。
  • 应该尽量避免在生产代码中使用,因为它表示尚未完成的工作。

派生新Context

派生新的Context意味着创建一个子Context,它继承了父Context的行为,并可以添加额外的功能,比如超时或携带值。

context.WithCancel
Go">ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保调用cancel以释放资源
  • 返回一个新的Context和一个取消函数cancel
  • 调用cancel()会取消ctx并关闭ctx.Done()通道。
context.WithDeadline
Go">deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
  • 如果当前时间超过了给定的截止时间,ctx.Done()将被关闭。
  • 即使时间到了,也应显式调用cancel()以确保资源被释放。
context.WithTimeout
Go">ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
  • 类似于WithDeadline,但它接受的是相对的时间间隔,而不是绝对的时间点。
  • 适合用于设置一个操作的最大持续时间。
context.WithValue
Go">type key stringctx := context.WithValue(context.Background(), key("user_id"), "12345")
  • 向Context添加键值对,允许你传递请求特定的数据。
  • 使用自定义类型(如上面的key)作为键可以避免冲突,并提高类型安全性。

监听Context的变化

当你的goroutine执行长时间运行的任务时,你可以监听ctx.Done()来检查是否应该提前退出任务。

Go">select {
case <-time.After(10 * time.Second):fmt.Println("Operation completed.")
case <-ctx.Done():fmt.Println("Operation was canceled:", ctx.Err())
}

这里我们使用select语句来等待两个条件之一:要么操作完成,要么Context被取消。如果Context被取消,则打印出取消的原因。

在网络服务中使用Context

在网络服务器中,context常用于控制HTTP请求的生命周期。例如,在标准库的net/http包中,每个请求都会有一个关联的context.Context对象,可以通过http.Request.Context()方法获取。

Go">func handler(w http.ResponseWriter, r *http.Request) {ctx := r.Context()select {case <-time.After(5 * time.Second):fmt.Fprintf(w, "Hello!")case <-ctx.Done():http.Error(w, ctx.Err().Error(), http.StatusInternalServerError)}
}

在这个例子中,如果请求被客户端取消或超时,ctx.Done()会被关闭,导致服务器停止处理请求并向客户端发送错误响应。

Context与数据库或其他长期运行的操作

当你执行可能需要很长时间才能完成的操作(如数据库查询),你应该始终监听传入的Context信号,以便在必要时中断操作。

Go">db, err := sql.Open("mysql", "...")
if err != nil {log.Fatal(err)
}// 使用带超时的Context
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()var name string
err = db.QueryRowContext(ctx, "SELECT name FROM users WHERE id=?", 1).Scan(&name)
if err != nil {if errors.Is(err, context.DeadlineExceeded) {log.Println("Query timed out")} else {log.Println("Query failed:", err)}
} else {fmt.Println("User name:", name)
}

以上展示了如何使用context与数据库交互,并设置了一个3秒的超时。如果查询未能在此时间内完成,操作将被取消。

综上所述,context包提供了一套机制,帮助你管理goroutine之间共享的数据,控制并发操作的生命周期,并优雅地处理超时和取消情况。正确使用context对于编写高效、可靠的服务端应用程序非常重要。


http://www.ppmy.cn/embedded/157167.html

相关文章

linux环境变量配置文件区别 /etc/profile和~/.bash_profile

在 Linux 系统中&#xff0c;环境变量可以定义用户会话的行为&#xff0c;而这些变量的加载和配置通常涉及多个文件&#xff0c;如 ~/.bash_profile 和 /etc/profile。这些文件的作用和加载时机各有不同。以下是对它们的详细区别和用途的说明&#xff1a; 文章目录 1. 环境变量…

SpringCloud两种注册中心

SpringCloud 基本概念 系统架构 我们之前做的所有的项目都属于单体架构&#xff0c;下面我们将要学习更适合大型项目的分布式架构 单体架构&#xff1a; 将业务的所有功能几种在一个项目中开发&#xff0c;打成一个包部署。 优点&#xff1a;架构简单、部署成本低 缺点&am…

差分轮算法-两个轮子计算速度的方法-阿克曼四轮小车计算方法

四轮驱小车的话&#xff1a; 转向角度计算方法&#xff1a;float turning_angle z_angular / x_linear; // 转向角度&#xff0c;单位为弧度 速度的话直接用线速度 两轮驱动小车&#xff1a; 计算公式&#xff1a; leftSpeed x_linear - z_angular * ORIGINBOT_WHEEL_TRACK /…

Baklib如何推动企业知识管理的创新与转型探讨

内容概要 在当今快速发展的数字化时代&#xff0c;企业需要不断适应变化&#xff0c;以保持竞争优势。Baklib作为一款企业知识管理中台&#xff0c;扮演着推动数字化转型的重要角色。它通过提供一个集成的知识管理平台&#xff0c;帮助企业高效管理和共享内部及外部的知识资源…

MySQL 中如何进行 SQL 调优?

重点 平时进行 SQL 调优,主要是通过观察慢 SQL,然后利用 explain 分析查询语句的执行计划,识别性能瓶颈,优化查询语句。 1) 合理设计索引,利用联合索引进行覆盖索引的优化,避免回表的发生,减少一次查询和随机 I/O 回表&#xff1a;索引无法满足查询所需的所有列数据&#xf…

双写+灰度发布:高并发场景下的维度表拆分零事故迁移实践

目录 0 文章摘要 1业务场景描述 2 迁移及实施过程 2.1 拆分设计与数据探查 2.1 历史数据迁移&#xff08;全量&#xff09; 2.3 增量数据同步&#xff08;双写过渡&#xff09; 2.4.业务切换验证 2.5 回滚预案 2.6 成果与收益 3 关键经验总结 往期回顾 专栏优势&am…

Redis-缓存

1.缓存 1.1 什么是缓存&#xff1f; 越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样;同样,实际开发中,系统也需要"避震器",防止过高的数据访问猛冲系统,导致其操作线程无法…

数据结构day02

1 线性表的定义和基本操作 1.1 线性表的定义 分析&#xff1a; 1.1.1 问题一&#xff1a;我们为什么探讨线性表的定义和基本操作 在研究数据结构时&#xff0c;需要重点关注三个方面&#xff1a;逻辑结构、物理结构以及数据的运算。在本节内容里&#xff0c;我们首先来介绍线…