学习笔记-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 传递少量与请求相关的上下文信息。
相关推荐
Nu11PointerException1 小时前
JAVA笔记 | ResponseBodyEmitter等异步流式接口快速学习
笔记·学习
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
亦枫Leonlew2 小时前
三维测量与建模笔记 - 3.3 张正友标定法
笔记·相机标定·三维重建·张正友标定法
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
考试宝2 小时前
国家宠物美容师职业技能等级评价(高级)理论考试题
经验分享·笔记·职场和发展·学习方法·业界资讯·宠物
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
黑叶白树4 小时前
简单的签到程序 python笔记
笔记·python
@小博的博客4 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
幸运超级加倍~5 小时前
软件设计师-上午题-15 计算机网络(5分)
笔记·计算机网络