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

Go

contextcontext.Backgroundcontext.TODOcontext.WithDeadlinecontext.WithValue

6.1.1 设计原理 #

context.Context
context.Context

golang-context-usage

图 6-1 Context 与 Goroutine 树

context.Contextcontext.Context

golang-without-context

图 6-2 不使用 Context 同步信号

context.Context

golang-with-context

图 6-3 使用 Context 同步信号

context.Contexthandle
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	go handle(ctx, 500*time.Millisecond)
	select {
	case <-ctx.Done():
		fmt.Println("main", ctx.Err())
	}
}

func handle(ctx context.Context, duration time.Duration) {
	select {
	case <-ctx.Done():
		fmt.Println("handle", ctx.Err())
	case <-time.After(duration):
		fmt.Println("process request with", duration)
	}
}

Go

因为过期时间大于处理时间,所以我们有足够的时间处理该请求,运行上述代码会打印出下面的内容:

$ go run context.go
process request with 500ms
main context deadline exceeded

Go

handleselectmainselectcontext.Contextmain context deadline exceeded

如果我们将处理请求时间增加至 1500ms,整个程序都会因为上下文的过期而被中止,:

$ go run context.go
main context deadline exceeded
handle context deadline exceeded

Go

context.Contextctx.Done()

6.1.2 默认上下文 #

contextcontext.Backgroundcontext.TODObackgroundtodo
func Background() Context {
	return background
}

func TODO() Context {
	return todo
}

Go

new(emptyCtx)context.emptyCtx
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
}

Go

context.emptyCtxcontext.Context

golang-context-hierarchy

图 6-4 Context 层级关系

context.Backgroundcontext.TODO
context.Background

6.1.3 取消信号 #

context.WithCancelcontext.Context

golang-parent-cancel-context

图 6-5 Context 子树的取消

context.WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

Go

func propagateCancel(parent Context, child canceler) {
	done := parent.Done()
	if done == nil {
		return // 父上下文不会触发取消信号
	}
	select {
	case <-done:
		child.cancel(false, parent.Err()) // 父上下文已经被取消
		return
	default:
	}

	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		if p.err != nil {
			child.cancel(false, p.err)
		} else {
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

Go

上述函数总共与父上下文相关的三种不同的情况:

parent.Done() == nilparentchildparentchildchildparentchildrenparentcontext.ContextDone()parent.Done()child.Done()parent.Done()child.cancel
context.propagateCancelparentchildparentchild
context.cancelCtxcontext.cancelCtx.cancel
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
	c.mu.Lock()
	if c.err != nil {
		c.mu.Unlock()
		return
	}
	c.err = err
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done)
	}
	for child := range c.children {
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

	if removeFromParent {
		removeChild(c.Context, c)
	}
}

Go

context.WithCancelcontextcontext.WithDeadlinecontext.WithTimeoutcontext.timerCtx
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}
	propagateCancel(parent, c)
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded) // 已经过了截止日期
		return c, func() { c.cancel(false, Canceled) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
	return c, func() { c.cancel(true, Canceled) }
}

Go

context.WithDeadlinecontext.timerCtxtime.AfterFunccontext.timerCtx.cancel
context.timerCtxcontext.cancelCtxtimerdeadline
type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
	return c.deadline, true
}

func (c *timerCtx) cancel(removeFromParent bool, err error) {
	c.cancelCtx.cancel(false, err)
	if removeFromParent {
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

Go

context.timerCtx.cancelcontext.cancelCtx.cancel

6.1.4 传值方法 #

contextcontext.WithValuecontext.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}
}

Go

context.valueCtxValueErrDeadlinecontext.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)
}

Go

context.valueCtxcontext.valueCtx.Valuenil

6.1.5 小结 #

context.Context
context.Context

6.1.6 延伸阅读 #