Golang 的 context Package 提供了一种简洁又强大方式来管理 goroutine 的生命周期,同时提供了一种 Requst-Scope K-V Store。
但是对于新手来说,Context 的概念不算非常的直观,这篇文章来带领大家了解一下 Context 包的基本作用和使用方法。
Context interface 是最基本的接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline()返回一个time.Time,是当前 Context 的应该结束的时间,ok 表示是否有 deadline
Done()返回一个struct{}类型的只读 channel
Err()返回 Context 被取消时的错误
Value(key interface{}) 是 Context 自带的 K-V 存储功能
canceler interface 定义了提供 cancel 函数的 context,当然要求数据结构要同时实现 Context interface
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
除了以上两个 interface 之外,context 包中还定义了若干个struct,来实现上面的 interface
emptyCtx是空的Context,只实现了Context interface,只能作为 root context 使用。
type emptyCtx int
cancelCtx继承了Context并实现了cancelerinterface,从WithCancel()函数产生
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
timerCtx继承了cancelCtx,所以也自然实现了Context和canceler这两个interface,由WithDeadline()函数产生
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
valueCtx包含key、val field,可以储存一对键值对,由WithValue()函数产生
type valueCtx struct {
Context
key, val interface{}
}
Context 只定义了 interface,真正使用时需要实例化,官方首先定义了一个 emptyCtx struct 来实现 Context interface,然后提供了Backgroud()函数来便利的生成一个 emptyCtx 实例。
实现代码如下
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
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
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
Backgroud() 生成的 emptyCtx 实例是不能取消的,因为emptyCtx没有实现canceler interface,要正常取消功能的话,还需要对 emptyCtx 实例进行派生。常见的两种派生用法是WithCancel() 和 WithTimeout。
调用WithCancel()可以将基础的 Context 进行继承,返回一个cancelCtx示例,并返回一个函数,可以在外层直接调用cancelCtx.cancel()来取消 Context
代码如下:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}
调用WithTimeout,需要传一个超时时间。来指定过多长时间后超时结束 Context,源代码中可以得知WithTimeout是WithDeadline的一层皮,WithDeadline传的是具体的结束时间点,这个在工程中并不实用,WithTimeout会根据运行时的时间做转换。
源代码如下:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: deadline,
}
propagateCancel(parent, c)
d := deadline.Sub(time.Now())
if d <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(d, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
在WithDeadline中,将 timeCtx.timer 挂上结束时的回调函数,回调函数的内容是调用cancel来结束 Context。
WithValue的具体使用方法在下面的用例中会讲。
源代码如下:
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}