Go中Context的应用场景

44 阅读4分钟

context的应用场景?

context 包是 Go 语言标准库提供的一个重要工具,用于在多个 Goroutine 之间传递取消信号、超时信息、截止时间以及请求范围的值等。它在编写并发代码时非常有用,特别是在处理请求的情况下。以下是一些 context 的主要应用场景:

  1. 取消信号传递
    • 当一个 Goroutine 启动了一些子 Goroutine 来执行任务时,可以使用 context 来向这些 Goroutine 发送取消信号,以便在主 Goroutine 接收到取消信号后,通知所有子 Goroutine 停止正在执行的任务。这种方式可以有效地控制 Goroutine 的生命周期,避免资源泄漏。
  1. 超时控制
    • 在网络请求或者执行一些 IO 操作时,我们希望能够在一定的时间内完成操作,如果超过了预期的时间,就放弃该操作或者进行一些处理。context 可以与 time 包结合使用,设置超时时间,当超时发生时,相关操作可以被取消或者中断。
  1. 截止时间
    • 类似于超时控制,但不同的是,截止时间通常是一个绝对时间点,而不是相对于当前时间的一段时间。例如,某个任务必须在某个特定的截止时间之前完成,否则就没有意义或者需要进行其他处理。
  1. 请求范围的值传递
    • 在一个请求处理流程中,可能需要在多个 Goroutine 中访问同一个请求相关的值(例如请求 ID、用户认证信息等)。context 可以用来在 Goroutine 之间传递这些值,而不需要在每个函数中手动传递这些值。
  1. 控制并发请求
    • 在进行并发请求时,可能需要控制同时进行的请求数量,以避免过载服务器或者服务提供商的限制。context 可以在每个请求中进行计数或者限制,并根据情况决定是否进行并发请求。

总之,context 包提供了一种优雅的方式来管理并发代码中的取消、超时、截止时间和值传递等问题,可以帮助开发者编写更加健壮和可靠的并发程序。

对应的代码例子:

1. 取消信号传递:

package main

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

func worker(ctx context.Context) {
	finishDataChan := make(chan struct{})
	go func() {
		// 模拟真实业务场景耗时:sql操作&IO请求操作
		time.Sleep(2 * time.Second)
		finishDataChan <- struct{}{}
	}()

	// 同时监听超时管道&业务请求完成管道,如果3秒到了,则进入取消信号case,说明业务处理超时,该worker进程也会结束,与之对应的业务处理子协程也会结束
	select {
	case <-ctx.Done():
		fmt.Println("Worker received cancellation signal.")
	case <-finishDataChan:
		fmt.Println("Working done...")
	}
}

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

	// Simulate main program execution
	time.Sleep(3 * time.Second)

	// Cancel the context to stop the worker
	cancel()

	// Wait for the worker to finish
	time.Sleep(1 * time.Second)
	fmt.Println("All done...")
}

真实的贴切的业务场景举例子:

package main

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

// getData模拟数据获取方法,接受一个context参数
func getData(ctx context.Context, id int) (string, error) {

    finishDataChan := make(chan struct{})
    go func() {
        // 业务场景,请求api拿数据&sql请求等
        time.Sleep(4 * time.Second)
        // 拿到了,发送信号,
        finishDataChan <- struct{}{}
    }()

    // 使用select来同时监听context的取消和模拟的耗时操作
    select {
        case <-finishDataChan:
        return fmt.Sprintf("数据 %d 获取成功", id), nil
        case <-ctx.Done():
        return "", fmt.Errorf("获取数据 %d 时被取消: %w", id, ctx.Err())
    }
}

// fetchData在单独的goroutine中调用getData,并处理超时
func fetchData(ctx context.Context, wg *sync.WaitGroup, id int) {
    defer wg.Done()

    // 调用getData并处理结果
    data, err := getData(ctx, id)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(data)
}

func main() {
    var wg sync.WaitGroup

    // 创建一个带有3秒超时的context
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel() // 在main函数结束时调用cancel,但超时会自动触发它

    // 启动多个goroutine来模拟并发数据获取
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go fetchData(ctx, &wg, i)
    }

    // 等待所有goroutine完成(或被取消)
    wg.Wait()
    fmt.Println("所有操作完成或已超时取消")
}

2. 超时控制:

package main

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

func operationWithTimeout(ctx context.Context) {
	select {
	case <-time.After(3 * time.Second): // Simulate some long operation
		fmt.Println("Operation completed.")
	case <-ctx.Done():
		fmt.Println("Operation canceled due to timeout.")
	}
}

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

	operationWithTimeout(timeoutCtx)
}

3. 截止时间:

package main

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

func operationWithDeadline(ctx context.Context) {
	deadline, ok := ctx.Deadline()
	if ok {
		fmt.Printf("Operation must be completed before: %s\n", deadline)
	} else {
		fmt.Println("No specific deadline for the operation.")
	}

	// Simulate some operation
	time.Sleep(2 * time.Second)

	select {
	case <-ctx.Done():
		fmt.Println("Operation canceled due to context deadline.")
	default:
		fmt.Println("Operation completed within the deadline.")
	}
}

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

	operationWithDeadline(deadlineCtx)
}

4. 请求范围的值传递:

package main

import (
	"context"
	"fmt"
	"sync"
)

func processRequest(ctx context.Context, requestID int) {
	// Accessing request-scoped value from the context
	userID, ok := ctx.Value("userID").(int)
	if !ok {
		fmt.Println("Failed to get userID from context.")
		return
	}

	fmt.Printf("Processing request %d for user %d\n", requestID, userID)
}

func main() {
	// Creating a parent context with a request-scoped value
	parentCtx := context.WithValue(context.Background(), "userID", 123)

	var wg sync.WaitGroup

	// Simulating multiple requests
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(requestID int) {
			// Creating a child context for each request
			childCtx := context.WithValue(parentCtx, "requestID", requestID)
			processRequest(childCtx, requestID)
			wg.Done()
		}(i)
	}

	wg.Wait()
}

5. 控制并发请求:

package main

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

type limitKey struct{}

func WithLimit(parent context.Context, limit int) (context.Context, context.CancelFunc) {
	// Use a buffered channel with the limit as its capacity
	sem := make(chan struct{}, limit)

	// Create a new context with a cancellation function
	ctx, cancel := context.WithCancel(parent)

	// Replace the value of the key with the semaphore
	return context.WithValue(ctx, limitKey{}, sem), cancel
}

func performRequest(ctx context.Context, requestID int) {
	// Access semaphore from context
	sem, _ := ctx.Value(limitKey{}).(chan struct{})

	// Acquire the semaphore
	sem <- struct{}{}
	defer func() { <-sem }() // Release the semaphore after request completes

	// Simulating some request processing
	// doing
	time.Sleep(1 * time.Second)
	fmt.Printf("Request %d completed.\n", requestID)
}

func main() {
	requests := []int{1, 2, 3, 4, 5}

	// Creating a context with a limit on concurrent requests
	concurrentCtx, cancel := WithLimit(context.Background(), 2)
	defer cancel()

	var wg sync.WaitGroup

	for _, requestID := range requests {
		wg.Add(1)
		go func(id int) {
			// Check if the context is canceled before performing the request
			select {
			case <-concurrentCtx.Done():
				fmt.Printf("Request %d canceled due to context cancellation.\n", id)
			default:
				performRequest(concurrentCtx, id)
			}
			wg.Done()
		}(requestID)
	}

	wg.Wait()
}