golang,go,博客,开源,编程

golang之context

Published on with 0 views and 0 comments

在 Go 语言中,context 包提供了一种在多个 goroutine 之间传递取消信号和请求作用域(如超时、截止时间等)的机制。context 在并发编程中扮演着重要角色,尤其是在处理 HTTP 请求、数据库操作等需要在多个 goroutine 间共享和传递状态的场景中。

1. context 的用途

  • 取消信号:可以通知多个 goroutine 停止工作,避免无用的资源消耗。
  • 超时控制:可以为一系列操作设置一个截止时间,超过这个时间自动取消。
  • 请求范围数据:可以在上下游 goroutine 间传递数据,通常用于传递请求 ID、认证信息等。

2. context 的基本概念

context 包中的主要概念是 Context 类型,它是一个接口,定义了用于取消信号、截止时间、超时和传递数据的方法。Context 实现是不可变的,每次创建新 Context 时会基于现有的 Context 扩展出新的状态。

3. context 的常见函数和方法

  • context.Background():返回一个空的、根 Context,通常用于整个程序的入口。
  • context.TODO():返回一个空的 Context,通常用于尚未决定具体实现时。
  • context.WithCancel(parent Context):基于 parent Context 创建一个新的 Context,并返回一个取消函数。调用该函数会取消这个新创建的 Context。
  • context.WithDeadline(parent Context, deadline time.Time):基于 parent Context 创建一个新的 Context,并设置一个截止时间,超过该时间会自动取消。
  • context.WithTimeout(parent Context, timeout time.Duration):基于 parent Context 创建一个新的 Context,并设置一个超时限制,超时后会自动取消。
  • context.WithValue(parent Context, key, value interface{}):基于 parent Context 创建一个新的 Context,用于传递数据(keyvalue)。

4. Context 方法

  • Done():返回一个 channel,当 Context 被取消时,该 channel 会被关闭,指示相关的工作可以停止。
  • Err():返回 Context 被取消时的错误信息,如果取消信号被发送,则返回 context.Canceledcontext.DeadlineExceeded
  • Value(key interface{}):获取与 key 相关联的值,通常用于传递请求上下文中的数据。

5. 示例代码

示例 1:取消信号与超时控制

package main

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

func doWork(ctx context.Context) {
	select {
	case <-time.After(5 * time.Second): // 模拟执行任务
		fmt.Println("Task completed.")
	case <-ctx.Done(): // 接收到取消信号
		fmt.Println("Task cancelled:", ctx.Err())
	}
}

func main() {
	// 创建一个带有超时限制的 Context
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	// 启动一个 goroutine执行任务
	go doWork(ctx)

	// 等待任务完成
	time.Sleep(6 * time.Second)
}

说明

  • context.WithTimeout:创建一个超时的 Context,超过 3 秒后,Context 会自动取消。
  • select 语句监听两个 channel:一个是 time.After,模拟任务的执行时间,另一个是 ctx.Done(),用于监听超时或取消信号。

输出:

Task cancelled: context deadline exceeded

示例 2:传递值(例如,传递请求 ID)

package main

import (
	"context"
	"fmt"
)

func processRequest(ctx context.Context) {
	// 从 Context 中获取请求 ID
	reqID := ctx.Value("request_id")
	fmt.Printf("Processing request with ID: %v\n", reqID)
}

func main() {
	// 创建一个带有值的 Context
	ctx := context.WithValue(context.Background(), "request_id", "123456")

	// 传递 Context 给 goroutine
	processRequest(ctx)
}

输出:

Processing request with ID: 123456

说明

  • context.WithValue:用于在 Context 中传递数据,这里传递了一个键为 "request_id" 的值 "123456"
  • 在 goroutine 中,调用 ctx.Value("request_id") 获取传递的数据。

6. Context 的最佳实践

  • 尽量将 Context 作为函数的第一个参数。大多数标准库函数都是如此做的,Go 社区也建议这样做。它应该用于传递请求上下文信息。
  • 避免过度使用 WithValueWithValue 主要用于传递轻量级的数据,如请求 ID、认证信息等。过度使用 WithValue 会使得代码复杂化,难以管理。
  • 在长时间运行的 goroutine 中传递 Context:对于可能需要取消的操作或需要超时控制的任务,使用 Context 来传递取消信号。
  • 使用 selectDone() 检测取消信号:通过 ctx.Done() 可以检测到 Context 的取消信号,及时停止工作,避免资源浪费。

7. 为什么要使用 context

  1. 控制 goroutine 的生命周期:例如,能够在任务执行过程中根据超时或者用户请求取消任务。
  2. 避免资源浪费:通过取消信号,及时中止不再需要的任务,避免资源浪费。
  3. 传递请求范围的数据:例如 HTTP 请求 ID、用户认证信息等,这些信息需要在多个函数和 goroutine 中传递,而不需要显式地传递作为参数。

8. ContextCancelFunc 的关系

每个通过 context.WithCancelcontext.WithDeadlinecontext.WithTimeout 创建的 Context 都会返回一个取消函数 (CancelFunc)。该函数用于触发取消操作。例如,调用 cancel() 会使得与该 Context 相关的 Done() channel 被关闭,从而触发取消信号。

ctx, cancel := context.WithCancel(context.Background())
// 在需要的时候调用 cancel 来取消 Context
cancel()

9. 小结

  • context 包提供了在多个 goroutine 中传递取消信号、超时限制和请求范围数据的功能,是并发编程中的重要工具。
  • 使用 context 可以实现任务的取消、超时控制,以及在函数间传递共享的请求信息。
  • context 对象本身是不可变的,每次操作都会返回一个新的 Context 对象。

标题:golang之context
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/01/07/1736220566569.html
联系:scotttu@163.com