前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang中为什么要有context,context常见的用法

golang中为什么要有context,context常见的用法

原创
作者头像
GeekLiHua
发布2024-08-30 22:51:09
900
发布2024-08-30 22:51:09
举报
文章被收录于专栏:go

golang中为什么要有context,context常见的用法

为什么要用context

在软件开发中,我们经常需要在函数调用链中传递一些信息,比如请求的截止时间、取消信号等。这些信息对于整个请求的处理流程至关重要。

context 提供了一种在 Go 程序中传递请求范围的值(例如,请求ID)和取消信号的方式。

context 是什么

context 是 Go 语言标准库中的一个包,它定义了一个 Context 类型,用于在 Go 程序中传递请求范围的值、取消信号和超时信息。简单来说,它是一个键值对的集合,可以在函数调用链中传递。

如何使用 context

  1. 创建 Context:
    • context.Background(): 创建一个新的、空的 context,通常用作根 context。
    • context.TODO(): 当代码中不知道应该使用哪个 context 时,可以使用这个函数。
  2. 取消 Context:
    • ctx, cancel := context.WithCancel(parentCtx): 创建一个可以在任何时候被取消的 context。parentCtx 是父 context。
    • cancel(): 调用这个函数可以取消 context,所有从这个 context 派生的子 context 也会被取消。
  3. 设置截止时间:
    • ctx, cancel := context.WithTimeout(parentCtx, timeout): 创建一个带有截止时间的 context。如果超时时间到了,context 会被自动取消。
  4. 设置超时时间:
    • ctx, cancel := context.WithDeadline(parentCtx, deadline): 创建一个带有截止时间点的 context。deadline 是一个时间点。
  5. 传递值:
    • ctx := context.WithValue(parentCtx, key, val): 向 context 中添加键值对。这些值可以在程序的任何地方被检索。
  6. 错误处理:
    • err := ctx.Err(): 检查 context 是否已经取消或超时,返回错误信息。
  7. 值检索:
    • val := ctx.Value(key): 从 context 中检索值。
  8. 使用 Context:
    • 在函数中,通常将 context 作为第一个参数,以支持取消操作和截止时间。
  9. Go 协程中的 Context 使用:
    • 在启动 Go 协程时,应该传递 context 给协程,以便协程可以响应取消信号。
  10. 示例代码:
代码语言:go
复制
    package main

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

    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        go func() {
            time.Sleep(3 * time.Second)
            cancel()
        }()

        select {
        case <-time.After(5 * time.Second):
            fmt.Println("Done")
        case <-ctx.Done():
            fmt.Println("Context was canceled:", ctx.Err())
        }
    }

在使用 context 时,要注意以下几点:

  • 不要在多个 Go 协程 中使用同一个 cancel 函数。
  • 避免在 context 中存储可变状态。
  • 避免在 context 中存储大的值,因为它们可能会被复制多次。

context的好处

  1. 取消操作:可以在请求不再需要时取消正在运行的任务。
  2. 超时控制:可以为请求设置超时时间,防止程序无限期等待。
  3. 传递请求范围的值:可以在不同的函数和 goroutine 之间传递请求相关的信息

业务场景:在线文件处理服务

在这个场景中,我们有一个在线服务,用户可以上传文件并请求处理,比如图像识别或数据分析。服务需要能够:

  1. 取消操作:如果用户决定不再需要处理结果,他们可以取消正在处理的任务。
  2. 超时控制:为了防止服务器资源被无限占用,我们为每个任务设置一个最大执行时间。
  3. 传递请求范围的值:我们需要在不同的服务组件之间传递用户ID、文件ID等信息,以确保任务的上下文一致性。
代码语言:c
复制
// 导入Go语言的包,用于程序的运行。
package main

import (
    "context" // 用于处理并发的包,提供取消操作和超时处理。
    "fmt"      // 用于格式化I/O操作的包。
    "os"       // 用于操作系统功能接口的包。
    "os/signal" // 用于监听操作系统信号的包。
    "sync"     // 用于同步原语的包,如互斥锁。
    "time"     // 用于时间相关操作的包。
)

// FileStatus 定义文件处理的状态结构,包含名称和描述。
type FileStatus struct {
    Name        string
    Description string
}

// 定义文件处理状态常量,初始化为不同的状态和描述。
var (
    StatusNotStarted FileStatus = FileStatus{Name: "NotStarted", Description: "未被处理"}
    StatusProcessing FileStatus = FileStatus{Name: "Processing", Description: "处理中"}
    StatusCanceled   FileStatus = FileStatus{Name: "Canceled", Description: "已取消"}
    StatusCompleted  FileStatus = FileStatus{Name: "Completed", Description: "处理成功"}
)

// FileContext 结构体用于维护文件的上下文信息,包括文件ID、用户ID、状态和互斥锁。
type FileContext struct {
    FileID string      // 文件的唯一标识符。
    UserID string      // 用户的ID。
    Status *FileStatus // 当前文件的状态。
    mutex  sync.Mutex  // 互斥锁,用于同步状态更新。
}

// updateFileStatus 函数用于安全地更新文件状态,通过互斥锁确保线程安全。
func updateFileStatus(fileCtx *FileContext, newStatus *FileStatus) {
    fileCtx.mutex.Lock() // 锁定互斥锁。
    fileCtx.Status = newStatus // 更新状态。
    fileCtx.mutex.Unlock() // 解锁互斥锁。
}

// printStatusLoop 函数是一个循环,用于定期打印文件的状态信息。
func printStatusLoop(fileCtx *FileContext, exitChan chan struct{}) {
    ticker := time.NewTicker(1 * time.Second) // 创建一个定时器,每秒触发一次。
    defer ticker.Stop() // 确保在函数结束时停止定时器。

    for {
       select {
       case <-ticker.C: // 等待定时器触发。
          fileCtx.mutex.Lock() // 锁定互斥锁。
          fmt.Printf("用户%s 正在处理文件 %s... 当前状态: %s\n", fileCtx.UserID, fileCtx.FileID, fileCtx.Status.Description) // 打印状态信息。
          fileCtx.mutex.Unlock() // 解锁互斥锁。
       case <-exitChan: // 等待退出信号。
          return // 退出循环。
       }
    }
}

// processFile 函数模拟文件处理任务,接受context用于控制任务取消,FileContext保存文件状态,exitChan用于通知状态打印循环退出。
func processFile(ctx context.Context, fileCtx *FileContext, exitChan chan struct{}) {
    fmt.Printf("用户%s 的文件 %s 开始处理... 当前状态: %s\n", fileCtx.UserID, fileCtx.FileID, fileCtx.Status.Description) // 打印开始处理信息。
    // 更新状态为正在处理,并打印初始状态。
    updateFileStatus(fileCtx, &StatusProcessing)

    // 启动一个goroutine来定期打印状态信息。
    go func() {
       for {
          select {
          case <-ctx.Done(): // 等待context通知完成或取消。
             return // 退出goroutine。
          default:
             fileCtx.mutex.Lock() // 锁定互斥锁。
             fmt.Printf("用户%s 正在处理文件 %s... 当前状态: %s\n", fileCtx.UserID, fileCtx.FileID, fileCtx.Status.Description) // 打印状态信息。
             fileCtx.mutex.Unlock() // 解锁互斥锁。
          }
          time.Sleep(1 * time.Second) // 休眠一秒。
       }
    }()

    // 模拟文件处理逻辑,10秒后自动完成。
    select {
    case <-ctx.Done(): // 等待context通知。
       if ctx.Err() == context.Canceled { // 检查是否被取消。
          updateFileStatus(fileCtx, &StatusCanceled) // 更新状态为已取消。
          fmt.Printf("用户%s 的文件 %s 被取消。\n", fileCtx.UserID, fileCtx.FileID) // 打印取消信息。
       }
       close(exitChan) // 任务完成或取消,关闭exitChan。
       return // 退出函数。
    case <-time.After(5 * time.Second): // 等待5秒。
       updateFileStatus(fileCtx, &StatusCompleted) // 更新状态为处理成功。
       fmt.Printf("用户%s 的文件 %s 处理成功。\n", fileCtx.UserID, fileCtx.FileID) // 打印成功信息。
       close(exitChan) // 任务完成,关闭exitChan。
    }
}

func main() {
    // 创建文件上下文,初始化文件ID、用户ID和状态。
    fileCtx := &FileContext{
       FileID: "file_789",
       UserID: "123456",
       Status: &StatusNotStarted,
       mutex:  sync.Mutex{},
    }

    // 创建退出通道,用于通知其他goroutine退出。
    exitChan := make(chan struct{})
    defer close(exitChan) // 确保在main结束前关闭退出通道。

    // 创建一个可以取消的context,用于控制文件处理任务的取消。
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // 确保在main结束前取消context。

    // 捕捉中断信号,用于处理用户中断操作,如Ctrl+C。
    signalChan := make(chan os.Signal, 1)
    signal.Notify(signalChan, os.Interrupt, os.Kill)

    // 启动一个goroutine来监听中断信号,并在接收到信号时调用cancel函数。
    go func() {
       sig := <-signalChan // 阻塞等待接收到信号。
       fmt.Println("接收到中断信号:", sig)
       cancel()        // 调用cancel来取消context。
       close(exitChan) // 关闭退出通道,通知其他goroutine退出。
    }()

    // 启动文件处理任务,作为goroutine运行。
    go processFile(ctx, fileCtx, exitChan)

    // 打印状态信息的循环可以在这里启动,如果需要的话。
    // 例如:
    // go printStatusLoop(fileCtx, exitChan)

    // 等待文件处理任务完成或被取消。
    select {
    case <-ctx.Done():
       // 此处不需要额外处理,因为processFile已经处理了状态更新。
    case <-exitChan:
       // 状态打印goroutine已经停止。
    }

    // 打印最终状态,通过锁定互斥锁保证线程安全。
    fileCtx.mutex.Lock()
    fmt.Printf("文件处理最终状态:%s\n", fileCtx.Status.Description)
    fileCtx.mutex.Unlock()

    // 清理,移除信号监听。
    signal.Stop(signalChan)

    // 正常退出程序。
    os.Exit(0)
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • golang中为什么要有context,context常见的用法
    • 为什么要用context
      • context 是什么
        • 如何使用 context
          • context的好处
            • 业务场景:在线文件处理服务
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档