golang中context详解

前言

编码中遇到上下文信息传递,并发信息取消等,记录下在go语言中context包的使用。


目录

  • 前言
  • context 介绍
  • 关键概念和使用方式
  • 基础使用
  • 进阶使用

context 介绍

在Go语言中,context包提供了一种在程序中传递截止日期、取消信号、请求范围数据和其他元数据的方式。context包的核心类型是Context接口,它定义了在执行上下文中传递的方法。Context接口的主要方法包括Deadline、Done、Err、Value等。

关键概念和使用方式

  1. Background: context.Background()函数返回一个空的Context,通常作为整个应用的顶层上下文,用于传递全局的信息。

  2. WithCancel: context.WithCancel函数创建一个带有取消信号的Context。当调用返回的cancel函数时,与该Context相关联的所有操作都会被取消。

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // 在不再需要时调用cancel
    
  3. WithTimeout和WithDeadline: context.WithTimeoutcontext.WithDeadline函数创建带有截止日期的ContextWithTimeout设置相对于当前时间的截止日期,而WithDeadline设置绝对时间的截止日期。

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    defer cancel() // 在不再需要时调用cancel
    
  4. WithValue: context.WithValue函数可以在Context中存储键值对,用于传递请求范围的数据。但要注意,过度使用WithValue可能导致上下文变得复杂,应慎重使用。

    ctx := context.WithValue(context.Background(), key, value)
    
  5. Done和Err: Done通道会在上下文被取消或达到截止日期时关闭。Err方法返回取消的原因,通常是context.Canceledcontext.DeadlineExceeded

    select {
    case <-ctx.Done():
        // 上下文被取消
        fmt.Println("Context canceled:", ctx.Err())
    }
    

context包的设计旨在简化在不同部分之间传递截止日期、取消信号和请求范围数据的方式,特别是在并发环境中。使用context包可以更好地管理资源、控制执行时间和处理取消操作。

基础使用

基础的context包使用示例,演示如何创建上下文、设置截止日期、取消上下文等基本操作:

package main

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

func main() {
	// 创建一个带有取消信号的上下文
	ctx, cancel := context.WithCancel(context.Background())

	// 启动一个goroutine执行任务,并传入上下文
	go performTask(ctx)

	// 模拟等待用户输入或其他触发条件
	fmt.Println("Press Enter to cancel the task.")
	fmt.Scanln()

	// 调用cancel函数取消上下文
	cancel()

	// 等待一段时间,以确保goroutine有足够的时间处理取消操作
	time.Sleep(time.Second * 2)
}

func performTask(ctx context.Context) {
	// 使用select监听上下文的取消信号
	select {
	case <-ctx.Done():
		// 当上下文被取消时执行的操作
		fmt.Println("Task canceled:", ctx.Err())
	default:
		// 模拟一个长时间运行的任务
		for i := 1; i <= 5; i++ {
			fmt.Println("Task is running...")
			time.Sleep(time.Second)
		}
		fmt.Println("Task completed successfully.")
	}
}

在这个示例中,我们首先创建一个带有取消信号的上下文,然后启动一个goroutine执行performTask函数。用户在控制台输入任意字符后,我们调用cancel函数取消上下文。performTask函数中使用select监听上下文的取消信号,一旦上下文被取消,任务将提前结束。在实际应用中,可以根据需要设置截止日期、传递数据等,以更灵活地管理上下文。

进阶使用

进阶的复杂示例,演示如何使用context包处理多个并发任务,并在某个任务失败或超时时取消所有任务:

package main

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"time"
)

func main() {
	// 创建一个带有取消信号的上下文,设置总体超时为5秒
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	// 使用WaitGroup等待所有任务完成
	var wg sync.WaitGroup

	// 启动多个goroutine执行任务
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go performTask(ctx, i, &wg)
	}

	// 等待所有任务完成或上下文取消
	wg.Wait()

	fmt.Println("All tasks completed.")
}

func performTask(ctx context.Context, taskID int, wg *sync.WaitGroup) {
	defer wg.Done()

	// 模拟每个任务的执行时间
	taskDuration := time.Second * time.Duration(rand.Intn(10))
	fmt.Printf("Task %d started. Duration: %s\n", taskID, taskDuration)

	// 使用WithTimeout创建一个子上下文,设置每个任务的超时时间
	subCtx, cancel := context.WithTimeout(ctx, taskDuration)
	defer cancel()

	select {
	case <-subCtx.Done():
		// 当子上下文被取消时执行的操作
		if subCtx.Err() == context.DeadlineExceeded {
			fmt.Printf("Task %d timed out.\n", taskID)
		} else {
			fmt.Printf("Task %d canceled.\n", taskID)
		}
	case <-time.After(taskDuration):
		// 模拟任务的实际工作
		fmt.Printf("Task %d completed successfully.\n", taskID)
	}
}

在这个示例中,我们创建了一个带有总体超时的上下文,并启动了多个goroutine执行performTask函数。每个任务都有一个随机的执行时间,并在子上下文中设置了超时。使用select监听子上下文的取消信号,一旦有任务失败或超时,它将及时结束。通过使用sync.WaitGroup等待所有任务完成,我们确保在所有任务完成或总体超时后程序可以退出。

这种方式可以更好地控制并发任务,确保及时释放资源并在需要时取消任务。在实际应用中,可以根据具体需求调整超时时间和处理逻辑。

你可能感兴趣的:(go,golang,golang)