context库

context 库提供了一种机制,用于跨 API 边界传递请求范围的值、取消信号以及请求超时等信息。通过使用 context,我们可以优雅地管理应用程序中的并发和 goroutine,并避免可能会导致死锁的竞争条件。

为什么需要 context

在实际的应用中,我们通常会有一些需要处理请求的操作,例如发送请求、访问数据库、调用其他 API 等。这些操作往往会使用 goroutine 并发执行,因此在进行这些操作时,我们需要协调它们之间的同步和协作。

在一个 HTTP 请求处理流程中,多个 goroutine 会在相同的上下文中协同工作,它们之间可能需要相互传递一些信息,例如:请求 ID、认证信息、日志记录器等。使用 context 可以帮助我们实现这些功能,并且在某些情况下,我们还可以使用 context 来管理超时和取消操作,从而提高应用程序的可靠性和稳定性。

context的组成

context 库由三个主要的组成部分构成:

  1. context.Context 接口:上下文接口定义了一组标准方法,包括获取上下文的值、取消上下文和检查是否已取消上下文。它是上下文管理的核心接口,由其他上下文相关的函数和方法使用。

  1. context.WithValue() 函数:这个函数可以基于原来的上下文创建一个新的上下文,同时在新上下文中添加一个键值对。它返回一个新的上下文实例,该实例包含在原上下文中存在的所有信息,同时还添加了新键值对。

  1. context.Background()context.TODO() 函数:context.Background() 函数返回一个空上下文,没有任何值和取消信号;context.TODO() 函数返回一个空上下文,没有任何值和取消信号,它通常被用来表示当前上下文不知道应该使用哪个上下文。

context 库还提供了一些其他的工具函数,如 context.WithCancel()context.WithDeadline()context.WithTimeout() 等,这些函数都是基于 context.Context 接口的标准方法和 context.WithValue() 函数构建而成的,可以帮助我们更方便地管理和使用上下文信息

context 的基本用法

context 可以理解为一个包含了请求范围的值和取消信号的容器。下面我们来看一下 context 的基本使用方式。

首先,我们需要在请求处理函数中创建一个 context 对象,该对象通常由外部传递进来,例如:

unchandler(ctx context.Context, req *http.Request) {
  // ...
}

然后,我们可以使用 context 对象中的方法来获取请求范围的值和取消信号,例如:

unchandler(ctx context.Context, req *http.Request) {
  // 获取请求 ID
  reqID := ctx.Value("reqID").(string)

  // 监听取消信号select {
  case <-ctx.Done():
    // 请求已经取消default:
    // 请求未取消
  }
}

在这个例子中,我们使用 ctx.Value() 方法来获取请求 ID,使用 ctx.Done() 方法来监听取消信号。当 ctx 对象中的 Done() 方法返回时,表示该请求已经被取消,我们可以通过读取 Err() 方法的返回值来确定请求被取消的原因。

接下来,我们可以使用 context 对象来派生新的 context 对象,例如:

unchandler(ctx context.Context, req *http.Request) {
  // 派生新的 context 对象
  ctx2, cancel := context.WithTimeout(ctx, 5*time.Second)
  defer cancel()

  // ...
}

在这个例子中,我们使用 context.WithTimeout() 方法来创建一个新的 context 对象,并指定该请求的处理时间不能超过 5 秒钟。在处理函数结束时,我们需要调用 cancel() 方法来释放相关的资源。

context 的实际应用

超时控制

package main

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

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

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("waited for 1 second")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }
}

上述代码中,我们使用 context.WithTimeout 创建了一个上下文,并设置了超时时间为 2 秒。在 select 语句中,我们先等待 1 秒钟,然后再监听上下文的 Done 信号。如果在 2 秒钟内没有收到 Done 信号,说明请求处理成功;如果在 2 秒钟内收到了 Done 信号,说明请求已经超时,我们可以输出超时信息。

取消操作

package main

import (
    "context""fmt"
)

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

    gofunc() {
        select {
        case <-time.After(3 * time.Second):
            fmt.Println("work done")
        case <-ctx.Done():
            fmt.Println(ctx.Err())
        }
    }()

    time.Sleep(1 * time.Second)
    cancel()
    time.Sleep(1 * time.Second)
}

上述代码中,我们使用 context.WithCancel 创建了一个上下文,并通过 cancel 函数提供了取消请求的功能。在 goroutine 中,我们使用 select 监听上下文的 Done 信号,如果在取消请求前收到了信号,说明请求已经取消。否则,我们会等待 3 秒钟后输出 "work done" 信息

上下文传递

package main

import (
    "context"
    "fmt"
)

type key int

const requestIDKey key = 0

func main() {
    ctx := context.Background()

    // 传递请求 ID 到上下文中
    ctx = context.WithValue(ctx, requestIDKey, "123")

    // 启动一个 goroutine,使用 context 进行传递
    go func(ctx context.Context) {
        requestID := ctx.Value(requestIDKey).(string)
        fmt.Println("request ID:", requestID)
    }(ctx)

    // 等待 goroutine 执行完毕
    time.Sleep(1 * time.Second)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值