Go-Context

介绍

在 Go 语言中,`context` 是一个用于在不同 Goroutine 之间传递请求范围数据、控制请求生命周期和处理取消信号的工具。它提供了一种机制来管理 Goroutine 之间的上下文信息,并允许在请求处理链中传递和取消上下文。

`context` 的主要优点和用途包括

1. 请求范围数据传递:`context` 允许在请求处理链中传递请求相关的数据,如请求 ID、用户身份验证信息等。这样可以避免在函数参数中传递大量的上下文信息。

2. 控制请求生命周期:`context` 允许设置超时时间、截止时间和取消信号,用于控制请求的处理时间和生命周期。当超时或取消信号触发时,相关的 Goroutine 可以及时退出,避免资源泄漏。

3. 处理取消信号:`context` 提供了一个取消机制,可以通过调用 `context` 的 `cancel` 方法来发送取消信号,通知相关的 Goroutine 停止处理请求。这对于处理长时间运行的请求或需要提前终止的情况非常有用。

应用场景

常见的 `context` 用法和应用场景包括

1.在 HTTP 服务器中使用`context` 传递请求相关的数据和控制请求的生命周期,如超时控制、请求取消等。

2.在并发任务中使用`context` 传递上下文信息和取消信号,以便在需要时中止任务的执行。- 在微服务架构中,使用 `context` 在服务之间传递请求范围的数据和控制请求的生命周期。

3.在测试中使用`context` 控制测试的超时时间和取消信号,以确保测试的稳定性和可靠性。通过使用 `context`,可以更好地管理和控制 Goroutine 之间的上下文信息和请求在日志记录中,使用 context 传递请求相关的数据,如请求 ID、用户信息等,以便在日志中记录相关的上下文信息。

4.在数据库操作中使用`context` 控制数据库查询的超时时间和取消信号,以避免长时间的阻塞和资源浪费

5.在调用远程服务或 API 时使`context `控制请求的超时时间和取消信号,以确保及时处理超时或取消的情况。

6.在任务调度和并发控制中使用 `context` 传递任务相关的数据和控制任务的生命周期,如任务取消、任务超时等。

Context,中文叫做上下文,Go语言在1.7版本中新增的context包中定义了ContextContext本质是一个接口,这个接口一共定义了四个方法:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}
  • Deadline 方法返回 context 的截止时间。如果没有设置截止时间,ok 为 false
  • Done 方法返回一个通道,当 context 被取消或过期时,这个通道会被关闭。
  • Err 方法返回 context 被取消或过期的原因。
  • Value 方法用于在 context 中存储和获取键值对。

使用方法

  • 不要把Context放在结构体中,要以参数的方式传递
  • 以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位
  • 给一个函数方法传递Context时,不要传递nil,如果不知道传递什么,就使用context.TODO()
  • Context的Value相关方法应该传递必须的参数,不要什么数据都使用这个传递
  • Context是线程安全的,可以放心的在多个goroutine中传递

context.Background

  • context.Background:返回一个空的 context,通常作为所有 context 树的根。它永远不会被取消,没有截止时间,也没有值。常用于 main 函数、初始化和测试代码。
ctx := context.Background()

context.TODO

context.TODO:用于暂时不知道使用哪个 context 的情况。它也是一个空的 context,但是建议尽快替换为合适的 context

ctx := context.TODO()

context.WithCancel

  • context.WithCancel:创建一个可取消的 context。返回一个新的 context 和一个取消函数 cancel。调用 cancel 函数时,会取消这个 context,所有依赖该 context 的操作都会收到取消信号。
package main

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

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done(): // 被取消的时候触发执行
            fmt.Println("worker stopped")
            err := ctx.Err() //  取消的原因
            fmt.Printf("context is cancelled, reason: %v\n", err)
            return
        default:
            fmt.Println("worker working")
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx)

    // 模拟一些工作
    time.Sleep(2 * time.Second)
    cancel() // 取消 context

    // 给 worker 一些时间来处理取消信号
    time.Sleep(1 * time.Second)
}

//worker working
//worker working
//worker working
//worker stopped
//context is cancelled, reason: context canceled

context.WithDeadline

  • context.WithDeadline:创建一个带有截止时间的 context。返回一个新的 context 和一个取消函数 cancel。当截止时间到达时,context 会自动取消。
package main

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

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("worker stopped due to deadline or cancellation")
            return
        default:
            fmt.Println("worker working")
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    deadline := time.Now().Add(2 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), deadline)
    defer cancel()

    go worker(ctx)

    // 给 worker 一些时间来运行
    time.Sleep(5 * time.Second)
}

//worker working
//worker working
//worker stopped due to deadline or cancellation

context.WithTimeout

  • context.WithTimeout:创建一个带有超时时间的 context。它是 context.WithDeadline 的便捷函数,根据当前时间和给定的超时时间计算截止时间。
package main

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

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("worker stopped due to timeout or cancellation")
            return
        default:
            fmt.Println("worker working")
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    go worker(ctx)

    // 给 worker 一些时间来运行
    time.Sleep(5 * time.Second)
}

//worker working
//worker working
//worker working
//worker stopped due to timeout or cancellation

context.WithValue

  • context.WithValue:创建一个新的 context,并将键值对存储在其中。
package main

import (
    "context"
    "fmt"
)

func processRequest(ctx context.Context) {
    value := ctx.Value("key")
    if val, ok := value.(string); ok {
        fmt.Printf("Value from context: %s\n", val)
    }
}

func main() {
    ctx := context.WithValue(context.Background(), "key", "Hello, World!")
    processRequest(ctx)
}

//Value from context: Hello, World!

流程图

  1. 创建 Context
    • 开始创建 Context,根据创建类型进入不同分支。
  2. 创建不同类型的 Context
    • emptyCtx:如果是 Background 或 TODO 创建方式,返回 emptyCtx,这是一个空的上下文,没有截止时间、取消功能和关联值。
    • cancelCtxWithCancel 创建方式会创建一个 cancelCtx 实例,并返回该实例和取消函数。
    • timerCtxWithDeadline 或 WithTimeout 创建方式会创建一个 timerCtx 实例并返回。timerCtx 基于 cancelCtx,带有截止时间功能。
    • valueCtxWithValue 创建方式会创建一个 valueCtx 实例并返回,用于在上下文中携带键值对。
  3. 取消流程:
    • 无论是手动调用取消函数(cancelCtx 的取消函数调用)还是截止时间到达(timerCtx 的截止时间触发),都会进入取消流程。
    • 取消函数调用后,首先设置 cancelCtx 的 err 字段,表示取消原因。
    • 接着关闭 cancelCtx 的 Done 通道,向所有监听该通道的 goroutine 发送取消信号。
    • 然后遍历并取消所有依赖该 context 的子 context,确保整个依赖链上的 goroutine 都能收到取消信号。
    • 最后结束取消流程。

exported_image (12)

 

posted @ 2023-07-05 05:26  GJH-  阅读(104)  评论(0)    收藏  举报