Golang笔记|Context

410 阅读3分钟

在 Go 语言中,context 是一个用于传递上下文信息、控制协程(goroutine)生命周期以及在协程之间传递取消信号的机制。它在并发编程中非常有用,可以用来优雅地管理协程的生命周期、传递超时和取消信号,以及避免资源泄漏。

常用的生成顶层context的方法:

  1. context.Background(): 返回一个非nil的、空的Context,没有任何值,不会被cancel,不会超时,没有截止日期。一般用在主函数、初始化、测试以及创建根 Context 的时候。
  2. context.TODO(): 返回一个非nil的、空的Context,没有任何值,不会被cancel,不会超时,没有截止日期。当你不清楚是否该用 Context,或者目前还不知道要传递一些什么上下文信息的时候,就可以使用这个方法。

context 包含了一些方法和类型,用于创建和操作上下文。以下是一些 context 的主要作用:

  1. 传递上下文信息: context 可以用于在协程中传递上下文信息,比如请求相关的信息、用户认证等。这可以避免在函数参数中一层层地传递这些信息。
  2. 控制生命周期: 通过 context,你可以取消协程的执行。当一个 context 被取消时,所有基于该 context 的协程都会收到取消信号,从而可以优雅地终止协程的执行。
  3. 传递超时和截止时间: 你可以使用 context 来设置协程的超时和截止时间,当超过指定的时间时,context 会被取消,从而取消相关的协程。

示例代码

传递上下文信息
package main

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

// 上下文信息传递

func authenticateRequest(ctx context.Context, userID int) {
   // 将用户ID存入上下文中
   ctx = context.WithValue(ctx, "userID", userID)
   fmt.Printf("User %d authenticated\n", userID)

   go processRequest(ctx, 1)
   go processRequest(ctx, 2)
}

func processRequest(ctx context.Context, requestID int) {
   // 从上下文中获取请求相关信息
   userID := ctx.Value("userID").(int)
   fmt.Printf("Processing request %d for user %d\n", requestID, userID)

   select {
   case <-time.After(2 * time.Second):
      fmt.Printf("Request %d processed\n", requestID)
   case <-ctx.Done():
      fmt.Printf("Request %d canceled due to context\n", requestID)
   }
}

func main() {
   var userID int
   userID = 123
   //userID = 1 触发ctx.Done的userID
   // 创建一个可取消上下文
   parentCtx, cancelParent := context.WithCancel(context.Background())
   // 调用身份验证函数,传递上下文
   authenticateRequest(parentCtx, userID)
   if userID == 1 {
      cancelParent() // 触发ctx.Done
   }
   // 等待一些时间,模拟主程序运行
   time.Sleep(3 * time.Second)
}
控制生命周期
package main

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

// context 控制生命周期

func worker(ctx context.Context, id int) {
   for {
      select {
      case <-ctx.Done():
         fmt.Printf("Worker %d: canceled\n", id)
         return
      default:
         fmt.Printf("Worker %d: working...\n", id)
         time.Sleep(1 * time.Second)
      }
   }
}

func main() {
   // 创建一个可以取消的上下文
   ctx, cancel := context.WithCancel(context.Background())
   defer cancel() // 确保在主函数退出时取消协程

   // 启动两个协程
   go worker(ctx, 1)
   go worker(ctx, 2)

   // 等待一段时间后取消协程的执行
   time.Sleep(3 * time.Second)
   fmt.Println("Main function: canceling workers...")
   cancel()                    // 取消协程的执行
   time.Sleep(1 * time.Second) // 给协程一点时间完成清理
   fmt.Println("Main function: done")
}
传递超时和截止时间
package main

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

func main() {
   // 创建一个具有 5 秒超时的 context
   ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   defer cancel() // 确保在主函数退出时取消 context

   // 启动一个协程来执行耗时操作
   go func(ctx context.Context) {
      select {
      case <-time.After(3 * time.Second):
         fmt.Println("Long operation completed successfully.")
      case <-ctx.Done():
         fmt.Println("Long operation canceled due to timeout.")
      }
   }(ctx)

   // 等待一段时间,确保协程有足够的时间执行
   time.Sleep(6 * time.Second)
   fmt.Println("Main function: done")
}