一文读懂Golang中的Context应用场景(含案例)

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. 取消信号传递:

go 复制代码
package main

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

func worker(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("Worker received cancellation signal.")
			return
		default:
			// Simulate some work
			time.Sleep(500 * time.Millisecond)
			fmt.Println("Working...")
		}
	}
}

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

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

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

	// Wait for the worker to finish
	time.Sleep(1 * time.Second)
}

2. 超时控制:

go 复制代码
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. 截止时间:

go 复制代码
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. 请求范围的值传递:

go 复制代码
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. 控制并发请求:

go 复制代码
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()
}
相关推荐
林壮Allen14 小时前
用 Go 从零实现多 Agent 协作框架:两个让我踩坑的设计问题
go·ai编程
代码方舟19 小时前
Go数据工程:利用天远柠檬查出险优化二手车批发拍卖车源出险核查合规体验
人工智能·go
不是万能的豆学长20 小时前
排查Go服务40天后的内存泄漏问题
开发语言·后端·golang·go
用户1257585243620 小时前
Golang后台管理框架推荐:XYGo Admin与devinggo怎么选?
后端·go·ai编程
tachibana221 小时前
Go语言里slice是怎么扩容的?
后端·golang·go·切片
tachibana22 天前
使用for range 的时候,它的地址会发生变化吗?
开发语言·后端·golang·go·语法
newerp2 天前
GC Pacer 调优算法:GOGC、GOMEMLIMIT 与容器 OOM 防御
后端·程序员·go
newerp2 天前
逃逸分析、栈分配与 sync.Pool 对象池
后端·程序员·go
Laurie三省3 天前
现场版却配上录音室版的歌词?聊聊我给开源 Mac 歌词软件写的多源打分算法
算法·macos·go
newerp4 天前
Execution Trace 深入实战:可视化调度与系统停顿分析
后端·程序员·go