context
解决的问题
- 取消信号传递
- 超时控制
- 请求范围的数据传递
- 避免全局变量
在 Go 中,context 包主要用于解决以下问题:
-
取消信号传递:
- 在多个 goroutine 之间传递取消信号,确保可以优雅地停止操作。例如,HTTP 请求被取消时,相关的 goroutine 也应停止。
-
超时控制:
- 为操作设置超时时间,防止长时间阻塞。可以通过
context.WithTimeout或context.WithDeadline实现。
- 为操作设置超时时间,防止长时间阻塞。可以通过
-
请求范围的数据传递:
- 在请求的生命周期中传递特定数据,如用户认证信息、请求 ID 等。
-
避免全局变量:
- 通过
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.WithCancel、context.WithTimeout 和 context.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())
}
这些机制通过通道实现了对取消信号的监听和传递,使得协程能够响应取消请求。
最佳实践列表。
- 不要将上下文存储在结构类型中
- 上下文应该贯穿您的程序。例如,对于 HTTP 请求,可以为每个传入请求创建一个新的上下文,该上下文可用于保存 request_id 或将一些常见信息(例如当前登录的用户)放入上下文中,这些信息可能对该特定请求有用。
- 始终将上下文作为第一个参数传递给函数。
- 每当您不确定是否使用上下文时,最好使用 context.ToDo() 作为占位符。
- 只有父 goroutine 或函数才应具有取消上下文。因此,不要将 cancelFunc 传递给下游 goroutine 或函数。Golang 允许您将 cancelFunc 传递给子 goroutine,但不建议这样做
放置顶层,作为第一个参数传递