学习笔记-Golang中的Context

文章目录

1、什么是Context

Context是 Go 语言中的一个标准库,用于在处理并发编程时,提供对请求的截止时间取消信号 以及请求范围内共享数据的管理。它在多个 goroutine 之间传递,是一种上下文管理工具。

Context 的主要目的是在不同的 goroutine 之间传递控制信号和数据。通过 Context,你可以:

  • 设置请求的超时时间或截止时间。
  • 传递取消信号,以便可以通知 goroutine 任务已经完成或需要停止。
  • 在多个 goroutine 之间共享值。

2、Context的作用

设想这样一种场景:你正在开发一个 Web 应用,当用户发送一个请求(request)后,可能会启动多个 goroutine 来处理该请求的不同部分。如果单个请求的 goroutine 结构比较简单,处理起来也不麻烦,但是如果启动多个、深层次的 goroutine,则会导致代码的复杂性和可维护性变得更加困难。而有了context,我们就可以通过它来管理这些 goroutine,并在它们之间传递控制信号和数据。

Context 在这种场景中的作用主要体现在以下几个方面:

  1. 取消信号传递:处理用户请求的过程中,可能会因为用户主动取消请求或请求超时而需要提前停止某些 goroutine 的执行。Context 提供了一种机制,可以在多个 goroutine 之间传递取消信号,当父 Context 取消时,所有与之关联的子 goroutine 都会收到取消通知并可以安全地停止工作,避免不必要的资源消耗。
  2. 数据共享:在处理用户请求时,某些全局信息(例如用户认证信息、追踪 ID 等)需要在不同的 goroutine 之间共享。通过 Context,这些数据可以被安全地传递,而不需要通过全局变量或其他不安全的方式共享。这样可以保持代码的简洁性和安全性,尤其在并发环境下避免数据竞争。
  3. 超时控制:在处理用户请求时,可能会遇到一些耗时长的操作,例如数据库查询或网络请求。Context 提供了一种机制,可以设置超时时间,当超时时间到达后,Context 会自动取消相关的 goroutine,避免资源占用过多。

除了上述作用外,Context 还可以更高效地管理系统资源,例如在请求超时或任务完成时主动释放资源,避免资源浪费,防止 goroutine 泄漏。

3、Context的解析

3.1、Context的源码解析

Context 实际上是一个定义了4个方法的接口,凡是实现了该接口的都称为一种 Context。

go 复制代码
type Context interface {
    // 返回 context 的截止时间(deadline),表示任务应该在什么时候之前完成。
    // 如果没有设置截止时间,就返回 false 和一个空的时间值。
    Deadline() (deadline time.Time, ok bool)

    //当 context 被取消或者超时时,这个方法返回的 channel 会被关闭。通过监听这个信号,可以决定是否要终止正在进行的任务。
    Done() <-chan struct{}

    //这个方法用来返回 context 被取消的具体原因,比如是因为超时、手动取消等,通常会在 Done() 关闭后使用。
    Err() error

     // 从 context 中获取与某个 key 关联的数据,用于在 goroutine 之间传递一些共享信息。
     // key 可以是任何类型,用于区分不同的数据。
    Value(key interface{}) interface{}
}

3.2、 context包中实现context接口的四种结构体类型

context包中,有四个结构体类型实现了Context接口,分别是emptyCtxcancelCtxtimerCtxvalueCtx

3.2.1、emptyCtx

emptyCtx 是一个没有任何取消、截止时间或值的 Context,它通常作为默认的上下文,在创建顶级 Context 时使用。

解释:
emptyCtx 实现了 Context 接口的所有方法,但它们都返回默认值或 nil,因此 emptyCtx`是没有实际功能的。那么它存在的目的是什么呢?

原因是,在 Go 的 Context 体系中,Context 对象通常是分层次的,即通过创建一个父 Context 来衍生出子 Context,并在这些子 Context 中传递控制信号、共享数据等。因此,为了构建这棵 Context 对象树,我们需要一个基础的、没有任何附加信息的根节点。emptyCtx 正是为了提供这样一个基本的起点。

此外,有时你在编写代码时需要一个 Context 占位符,但暂时不确定具体的 Context 类型。这时你可以使用 context.TODO(),它返回的实际上也是 emptyCtx。这样可以让代码先运行,稍后再决定具体的 Context 实现。

总之,emptyCtx 是一个没有任何功能的 Context,它的使用场景有两个:

  1. 用于顶层上下文,作为 Context 对象树的根节点。
  2. 作为占位符,用于在编写代码时暂时不确定具体的 Context 类型。

注意 :这四种结构体中,只有emptyCtx不需要 通过已有context实例创建,因此一个context树的根context一定是emptyCtx

使用示例:

go 复制代码
ctx := context.Background()
// 或者
ctx := context.TODO()
// 以下代码中,ctx 就是一个空的上下文

3.2.2、cancelCtx

cancelCtx 是带有取消功能的 Context,它允许父 Context 取消,并通知所有子 Context 和相关 goroutine。

解释:

  • cancelCtx 通过 WithCancel 函数创建,提供一种手动取消 Context 的机制。当调用 cancel() 函数时,所有依赖此 Context 的子 goroutine 都会收到取消通知。
  • Done() 方法会返回一个关闭的 channel,表示 Context 已经被取消。

使用场景:

  1. 用于主动取消某个操作,比如超时、用户主动取消等。
  2. 适用于请求处理被取消、提前终止任务等场景。

使用示例:

go 复制代码
package main

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

func main() {
    // 通过emptyCtx创建一个带有取消功能的上下文(context.Background()创建的即为emptyCtx)
    ctx, cancel := context.WithCancel(context.Background())

    go func() {
        // 模拟执行任务
        for {
            select {
            case <-ctx.Done(): // 接收到取消信号,退出 goroutine
                fmt.Println("Task canceled")
                return
            default:
                fmt.Println("Task running...")
                time.Sleep(1 * time.Second)
            }
        }
    }()

    // 运行 3 秒后取消操作
    time.Sleep(3 * time.Second)
    cancel()

    // 给 goroutine 充分的时间来处理取消操作
    time.Sleep(1 * time.Second)
}

注意:当 Done() 信号触发时,虽然上下文 Context 已经被取消,但具体的 goroutine 或子进程是否终止,取决于代码逻辑。cancel() 只是通知 goroutine Context 已被取消,具体的退出逻辑需要我们手动处理。

3.2.3、timerCtx

timerCtx 是带有超时或截止时间(Deadline)的 Context,当超过指定时间时,Context 会自动取消。

解释:

  • timerCtx 是通过 WithTimeoutWithDeadline 创建的。它允许你为 Context 设置一个时间限制,当超时或达到截止时间时,Context 会自动取消,所有关联的 goroutine 都会收到取消通知。
  • 主要用于那些有执行时间限制的任务。

使用场景:

  1. 当你需要控制某个任务的最大执行时间(如网络请求、数据库操作等),防止任务无限期地运行时,使用 timerCtx。
  2. 适用于设置请求超时、操作超时等场景。

使用示例:

go 复制代码
// // 通过emptyCtx创建一个带有取消功能的上下文(context.Background()创建的即为emptyCtx)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

select {
case <-time.After(3 * time.Second):
    fmt.Println("Task finished")
case <-ctx.Done():
    fmt.Println("Timeout:", ctx.Err()) // 这里会输出超时的错误
}

3.2.4、valueCtx

valueCtx 是可以在 goroutine 之间传递键值对的 Context,用于携带与请求相关的信息。context.WithValue() 可以将特定的值与某个 key 关联起来,并且这个 key 通常是一个自定义类型,而不是直接使用内置的 string 或 int 类型。

解释:
valueCtx 是通过 WithValue 创建的,用于在不同的 goroutine 之间传递一些与上下文相关的数据。这个数据可以通过 Context 的Value()方法获取。 context.WithValue() 可以将特定的值与某个 key 关联起来,并且这个 key 通常是一个自定义类型,而不是直接使用内置的数据类型,如stringint 类型。

使用场景:

  1. 适用于在多个 goroutine 之间传递一些共享信息,如用户 ID、请求 ID 等。
  2. 避免使用全局变量或复杂的依赖传递,确保上下文中的数据和 goroutine 的生命周期一致。

使用示例:

go 复制代码
type key string

func main() {
    ctx := context.WithValue(context.Background(), key("userID"), 123)

    go func(ctx context.Context) {
        userID := ctx.Value(key("userID")) // 定义了一个新的类型 key,底层类型为 string
        fmt.Println("User ID:", userID)
    }(ctx)

    time.Sleep(1 * time.Second)
}

注意 :在上述代码中,如果直接使用 string 类型作为 key ,很可能会出现 key 重名的情况。比如,不同的模块可能会不小心使用相同的字符串 "userID" 作为 key ,这会导致冲突。因此,为了避免这种情况,我们会创建一个自定义类型的 key ,比如 type key string 。这样,尽管它本质上仍然是字符串类型,但它已经是一个新的类型,这样就避免了潜在的冲突问题。

4、总结

Context 是 Go 语言中的一个标准库,用于在处理并发编程时,提供对请求的截止时间、取消信号以及请求范围内共享数据管理的管理。它在多个 goroutine 之间传递,是一种上下文管理工具。

通过context包给出的四种结构体类型,我们可以轻松实现对 goroutine 的管理:

  • emptyCtx:基础的空 Context,没有取消、超时或值,适合作为顶层 Context 使用。
  • cancelCtx:提供手动取消功能,适合需要手动控制任务生命周期的场景。
  • timerCtx:带有超时或截止时间,适合限制任务执行时间的场景。
  • valueCtx:允许传递数据,适合跨 goroutine 传递少量与请求相关的上下文信息。
相关推荐
程序员大雄学编程9 小时前
「机器学习笔记7」决策树学习:从理论到实践的全面解析(上)
笔记·决策树·机器学习
larry_dongy9 小时前
【学习记录】vscode+ros2+cpp调试
vscode·学习
递归不收敛10 小时前
吴恩达机器学习课程(PyTorch适配)学习笔记:1.5 决策树与集成学习
pytorch·学习·机器学习
菜鸟‍10 小时前
【论文学习】2025年图像处理顶会论文
图像处理·人工智能·学习
Logintern0910 小时前
【学习篇】Redis 分布式锁
redis·分布式·学习
聪明的笨猪猪10 小时前
Java Spring “Bean” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
A9better10 小时前
嵌入式开发学习日志38——stm32之看门狗
stm32·嵌入式硬件·学习
bnsarocket11 小时前
Verilog和FPGA的自学笔记3——仿真文件Testbench的编写
笔记·fpga开发·verilog·自学
丰锋ff11 小时前
2025 年真题配套词汇单词笔记(考研真相)
笔记·考研
Vizio<13 小时前
《基于 ERT 的稀疏电极机器人皮肤技术》ICRA2020论文解析
论文阅读·人工智能·学习·机器人·触觉传感器