Go 1.7 标准库引入 context,中文译作“上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。Go 语言中的 context.Context 的主要作用还是在多个 Goroutine 组成的树中同步取消信号以减少对资源的消耗和占用。
接口实现
context.Context其实是一个接口,提供了以下4种方法:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
- Deadline方法的第一个返回值表示还有多久到期,第二个返回值表示是否到期。
- Done是使用最频繁的方法,其返回一个通道,一般的做法是监听该通道的信号,如果收到信号则表示通道已经关闭,需要执行退出。
- 如果通道已经关闭,则Err()返回退出的原因。
- Value方法返回指定key对应的value,这是context携带的值
context退出与传递
调用context.Background函数或context.TODO函数会返回最简单的context实现。context.Background函数一般作为根对象存在,其不可以退出,也不能携带值。要具体地使用context的功能,需要派生出新的context,以下三个函数用于处理退出。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithCancel函数返回一个子context并且有cancel退出方法。子context在两种情况下会退出,一种情况是调用cancel,另一种情况是当参数中的父context退出时,该context及其关联的子context都将退出。
WithTimeout函数指定超时时间,当超时发生后,子context将退出。因此子context的退出有3种时机,一种是父context退出;一种是超时退出;一种是主动调用cancel函数退出。WithDeadline和WithTimeout函数的处理方法相似,不过其参数指定的是最后到期的时间。WithValue函数返回带key-value的子context。
context传值
context还有一个用途:它提供了一种方法在程序中传递每个请求的元数据。
context 包中的 context.WithValue能从父上下文中创建一个子上下文,传值的子上下文使用 context.valueCtx类型:
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
context.valueCtx结构体会将除了 Value 之外的 Err、Deadline等方法代理到父上下文中,它只会响应 context.valueCtx.Value 方法,该方法的实现也很简单:
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.valueCtx 中存储的键值对与context.valueCtx.Value 方法中传入的参数不匹配,就会从父上下文中查找该键对应的值直到某个父上下文中返回nil或者查找到对应的值。
参考资料
- 《Go语言底层原理剖析》
- 《Go 语言设计与实现》
- 《Go语言学习指南:惯例模式与编程实践》