Golang 中的 Context | 青训营笔记

83 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

Context 接口

Golang 中的 Context 接口提供了上下文消息传递的能力。

type Context interface {
    Deadline() (deadline time.Time, ok bool)

    Done() <-chan struct{}

    Err() error

    Value(key interface{}) interface{}
}
  • Deadline 方法返回这个 Context 对象应该结束的时间,ok 表示是否设置返回时间
  • Done 方法返回一个只读的 chan。当这个 chan 可以读取时,意味着父级 Context 已经发起了结束请求。此时应该进行清理操作,退出 goroutine,释放资源
  • Err 方法返回错误信息
  • Value 方法用于获取 Context 上绑定的值。Context 中变量的存储采用键值对的形式,因此需要传递一个 key 来获取。取值操作通常是协程安全的。

Context 的继承

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)

func Background() Context {
    return background
}

func TODO() Context {
    return todo
}

Golang 针对 Context 接口提供了两个默认实现的对象。

  • Background 方法返回的 Context 主要应用于 main 函数、初始化和测试场景,位于整个 Context 树的根节点,是顶级 Context
  • TODO 方法返回的 Context 主要在尚不清楚具体应用场景时作为占位符使用(官方文档中指出不建议使用值为 nilContext

这两个 Context 本质上都是 emptyCtx 类型。是一个没有设置结束时间、不可结束、没有携带任何值的 Context

type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    return
}

func (*emptyCtx) Done() <-chan struct{} {
    return nil
}

func (*emptyCtx) Err() error {
    return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
    return nil
}

Context 的继承衍生

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

context 包提供了 With 系列函数用于衍生更多子 Context。这些函数都默认传入一个 Context 对象作为父级 Context,并返回一个衍生出的子 Context 对象(可以视为对父级 Context 的继承)。

WithCancel 函数

WithCancel 函数传递一个父 Context 作为参数,返回子 Context,以及一个取消函数用来结束 Context

WithDeadline 函数

WithDeadline 函数会多传递一个截止时间参数,意味着到了这个时间点,会自动取消 Context。当然在实际使用中也可以提前通过结束函数进行结束。

WithTimeout 函数

WithTimeout 函数表示是超时自动取消。

WithValue 函数

与上述三个函数不同,WithValue 函数返回的是原始父级对象的一个副本,作用是绑定一个键值对数据,这个绑定的数据可以被 Context.Value 方法访问到。

其中,键的类型必须是可比较的,并且不应是 string 类型或其他内置类型。实际使用中应该为键值自定义类型。

Context 使用原则

  • 不要在结构体中声明 Context 类型的字段,而是将 Context 对象显式地传递给需要的函数
  • 使用 Context 作为参数的函数和方法,应当将 Context 作为第一个参数,且参数命名通常为 ctx
  • 不要传递值为 nilContext。在不清楚使用场景时使用 context.TODO()
  • Context 仅用于不同进程、goroutine 之间进行数据传输,而不是用于普通函数之间参数的传递
  • Context 对于多个 goroutine 是协程安全的