Skip to main content

context

解决的问题

  • 取消信号传递
  • 超时控制
  • 请求范围的数据传递
  • 避免全局变量

在 Go 中,context 包主要用于解决以下问题:

  1. 取消信号传递:

    • 在多个 goroutine 之间传递取消信号,确保可以优雅地停止操作。例如,HTTP 请求被取消时,相关的 goroutine 也应停止。
  2. 超时控制:

    • 为操作设置超时时间,防止长时间阻塞。可以通过 context.WithTimeoutcontext.WithDeadline 实现。
  3. 请求范围的数据传递:

    • 在请求的生命周期中传递特定数据,如用户认证信息、请求 ID 等。
  4. 避免全局变量:

    • 通过 context 传递数据和信号,减少对全局变量的依赖,提高代码的可维护性和测试性。

context 是在并发编程中管理生命周期和控制流的一个重要工具。

context.WithValue 实现原理

context.WithValue 的实现并不是通过 Go 的继承机制,而是通过组合和链式查找实现的。

原理

  • 组合:

    • context.WithValue 创建一个新的 context,这个新 context 包含一个指向父 context 的引用,以及一个新的键值对。
  • 链式查找:

    • 当调用 Value 方法时,首先检查当前 context 是否包含该键。
    • 如果没有找到,则继续向父 context 查找,直到找到匹配的键或到达根 context

结构

每个 context 都是一个接口,具体实现通常是一个结构体,这个结构体包含了父 context 的引用和自己的数据。

示例

type valueCtx struct {
Context
key, val interface{}
}

func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}

这种设计模式类似于组合而不是继承,通过层层嵌套实现数据的传递和查找。

context.WithCancel() 、context.WithTimeout() 、context.WithDeadline() 实现原理是通过 chan ?

是的,context.WithCancelcontext.WithTimeoutcontext.WithDeadline 的实现原理主要是通过通道(channel)来实现的。

实现原理

  • context.WithCancel:

    • 创建一个可取消的子 context
    • 返回的 context 包含一个 Done 通道,当调用取消函数时,该通道会被关闭。
  • context.WithTimeout:

    • 在指定的超时时间后自动取消。
    • 内部使用了 context.WithCancel,并启动一个计时器,在超时后关闭 Done 通道。
  • context.WithDeadline:

    • 类似于 context.WithTimeout,但可以指定一个具体的截止时间。
    • 内部也是使用 context.WithCancel,并在达到截止时间时关闭 Done 通道。

示例

ctx, cancel := context.WithCancel(parentCtx)
defer cancel() // 确保在不再需要时取消

select {
case <-ctx.Done():
fmt.Println("Cancelled:", ctx.Err())
}

这些机制通过通道实现了对取消信号的监听和传递,使得协程能够响应取消请求。

最佳实践列表。

  1. 不要将上下文存储在结构类型中
  2. 上下文应该贯穿您的程序。例如,对于 HTTP 请求,可以为每个传入请求创建一个新的上下文,该上下文可用于保存 request_id 或将一些常见信息(例如当前登录的用户)放入上下文中,这些信息可能对该特定请求有用。
  3. 始终将上下文作为第一个参数传递给函数。
  4. 每当您不确定是否使用上下文时,最好使用 context.ToDo() 作为占位符。
  5. 只有父 goroutine 或函数才应具有取消上下文。因此,不要将 cancelFunc 传递给下游 goroutine 或函数。Golang 允许您将 cancelFunc 传递给子 goroutine,但不建议这样做

放置顶层,作为第一个参数传递