Go 语言实现——Context

首先,Context 是一个接口。

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

context 包中提供了 4 种类型的 Context:emptyCtx、cancelCtx、timerCtx、valueCtx,这 4 种类型的 Context 通过 Embedding 的方式有如下的继承关系。

../_images/go-context.png

context.Background()context.TODO() 返回 emptyCtx,这个 Context 不会被 cancel,没有 deadline,也没有 value,emptyCtx 就是一个普通的 int,int 值没有意义。这个 context 一般被用作 root context。

type emptyCtx int

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

func Background() Context {
    return background
}

func TODO() Context {
    return todo
}

调用 context.With* 函数可以创建新的 Context,这些 Context 会从 root context 开始构成一个树,树的节点带有指向父节点的指针,如:

context.WithTimeout(context.WithCancel(context.Background()), 3*time.Second)
context.WithValue(context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)), "mykey", 1)

代码执行后会生成如下的 context 结构:

../_images/go-context-tree.png

context.WithDeadlinecontext.WithTimeout 都是创建的 timerCtx,context.WithTimeoutcontext.WithDeadline 的一个简单封装。

context.WithCancel 来看下 context.With* 干的事情:

type cancelCtx struct {
    Context

    mu       sync.Mutex
    done     chan struct{}
    children map[canceler]struct{}
    err      error
}

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    if parent == nil {
        panic("cannot create context from nil parent")
    }
    // 1. 创建一个新的 cancelCtx 并保存指向父 context 的指针。
    c := newCancelCtx(parent)
    // 2. 在父 context 中保存指向新建的 cancelCtx 的指针
    //    父 context 取消会导致所有的子 context 一起取消。
    propagateCancel(parent, &c)
    // 3. 返回新建 cancelCtx 的指针以及取消函数
    //    c.cancel 是私有函数,没法直接调用,只能通过取消函数取消。
    return &c, func() { c.cancel(true, Canceled) }
}

func newCancelCtx(parent Context) cancelCtx {
    return cancelCtx{Context: parent}
}