Blog icon indicating copy to clipboard operation
Blog copied to clipboard

Context 包解析

Open LLLeon opened this issue 8 years ago • 0 comments

Context 用来管理 goroutine 的上下文,包括传递请求作用域的值、退出信号和处理一个请求时涉及的所有 goroutine 的截止时间等。

代码解析

// Context 的方法可以由多个协程同时调用
type Context interface {
    // Deadline 返回代表这个 context 的工作取消的时间
    // 当没有设置截止时间时,ok==false
    // 连续调用 Deadline 时只有第一次生效,后面都返回同样的结果
	Deadline() (deadline time.Time, ok bool)
    
    // Done 返回一个关闭的 channel,代表这个 context 的工作应该被取消。如果这个 context 永远不能取消则应该返回 nil。连续调用时只有第一次生效,后面都返回同样的结果
	Done() <-chan struct{}

	Err() error

	Value(key interface{}) interface{}
}

// 当 context 退出时 Context.Err 返回的错误
var Canceled = errors.New("context canceled")

// 当过了 context 的截止时间后,Context.Err 返回的错误
var DeadlineExceeded error = deadlineExceededError{}

type deadlineExceededError struct{}

// 没有接收器名称的方法,这里用的是非指针的 deadlineExceededError。为什么???
func (deadlineExceededError) Error() string   { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool   { return true }
func (deadlineExceededError) Temporary() bool { return true }

// 永远不退出,没有值,没有截止时间。它不是结构体,因此这个类型的变量必须有确定的地址
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)
)

// 实际是 emptyCtx,通常用于 main 函数、初始化和测试
func Background() Context {
	return background
}

// 也是 emptyCtx,当不清楚用那种 context 或还没有可用的 context 时使用
func TODO() Context {
	return todo
}

// 定义一个函数类型,调用时立即停止某项操作,只在第一次调用时生效,后续调用什么都不做
type CancelFunc func()

// 复制一份 parent context,此副本作为 parent 的 child 并遵循其退出行为。
// 当调用 cancel 函数时或 parent 的 Done channel 关闭时,ctx 的 Done channel 将关闭,无论哪一个先发生。
// 返回 context 的副本和一个用来将此副本从 parent 的 children 中移除并退出的函数
// 应当在运行在此 context 中的工作完成时执行此 CancelFunc,context 退出时将释放与之相关的资源
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) } // 返回的还是地址,interface 可以是指针
}

// 返回一个初始化的 cancelCtx
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

// 判断 parent 是否退出,其 child 也遵循 parent 的退出行为
func propagateCancel(parent Context, child canceler) {
	if parent.Done() == nil {
		return // 表示 parent 实际是 emptyCtx,永不退出
	}
	if p, ok := parentCancelCtx(parent); ok { // 判断 parent 是不是 cancelCtx 类型
		p.mu.Lock()
		if p.err != nil {
			// p.err 有值,说明 parent 已经退出,child 也将退出,但不从其 parent 的 children 中移除
			child.cancel(false, p.err)
		} else {
              // 如果 parent 没有退出且还没有 child,则将此 child 作为其 child
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{} // 值不重要,只是一个空 struct
		}
		p.mu.Unlock()
	} else {
         // 如果 parent 没有退出,启动协程等待 parent 和 child 退出
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

// 循环检测 parent 的实际类型,直到发现是 *cancelCtx 时返回其指针和 true
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	for {
		switch c := parent.(type) {
		case *cancelCtx: // 和上面 WithCancel 一样,这里也是指针
			return c, true
		case *timerCtx:
			return &c.cancelCtx, true
		case *valueCtx:
			parent = c.Context // 继续循环
		default:
			return nil, false // 上面的 case 都没有匹配到时,说明是 background 或 TODO类型,直接返回
		}
	}
}

// 从 parent 中移除 child
func removeChild(parent Context, child canceler) {
	p, ok := parentCancelCtx(parent) // 判断 parent 类型
	if !ok {
		return
	}
	p.mu.Lock()
	if p.children != nil {
		delete(p.children, child)
	}
	p.mu.Unlock()
}

// 是 context 类型,可以直接退出。*cancelCtx 和 *timerCtx 是其具体实现
type canceler interface {
	cancel(removeFromParent bool, err error)
	Done() <-chan struct{}
}

// 可重复使用的关闭的通道
var closedchan = make(chan struct{})

func init() {
	close(closedchan) // 一开始就关闭 closedchan
}

// cancelCtx 可以退出,当退出时,它也取消其任何实现了 canceler 的 children
type cancelCtx struct {
	Context

	mu       sync.Mutex            // 保护以下字段
	done     chan struct{}         // created lazily, 通过第一个取消调用来关闭
	children map[canceler]struct{} // 通过第一个取消调用将其设置为 nil
	err      error                 // 通过第一个取消调用将其设置为非 nil
}

func (c *cancelCtx) Done() <-chan struct{} {
	c.mu.Lock()
	if c.done == nil {
		c.done = make(chan struct{})
	}
	d := c.done
	c.mu.Unlock()
	return d
}

func (c *cancelCtx) Err() error {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.err
}

func (c *cancelCtx) String() string {
	return fmt.Sprintf("%v.WithCancel", c.Context)
}

// 这个函数关闭 c.done,退出其每个 child,如果 removeFromParent 是 true,
// 则将 c 从其 parent 的 children 中移除
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
	if err == nil {
		panic("context: internal error: missing cancel error")
	}
	c.mu.Lock()
	if c.err != nil {
		c.mu.Unlock()
		return // 说明已经退出过,直接返回
	}
	c.err = err
    // 如果没有 done,则将关闭的 channel 赋值给它,如果有则关闭
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done)
	}
    // 将 c 的 children 都退出,注意这里的实参 false 和上面的 removeFromParent 没有关系,
    // 因为 removeFromParent 是用来决定是否将 c 从其 parent 的 children 中移除
	for child := range c.children {
        // 当持有 parent 的锁时也就获取了 child 的锁,可以直接操作
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

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

// 返回 parent 的一个副本,其截止时间是 deadline。如果 parent 的截止时间比 deadline 早,
// 则新 context 在语义上等同于 parent。当时间过了 deadline、cancel 函数被调用、parent 的
// Done channel 关闭时,新 context 的 Done channel 将被关闭,无论哪种情况先发生。
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
	if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
		// 只有 timerCtx 的 Deadline 返回 true,如果 parent 的截止时间早于 deadline,
        // 直接返回带有 cancel 函数的 context
		return WithCancel(parent)
	}
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  deadline,
	}
	propagateCancel(parent, c) // 遵循 parent 的退出行为
	d := time.Until(deadline)
	if d <= 0 {
         // 已经过了 deadline,将 c 退出并从其 parent 的 children 中移除
		c.cancel(true, DeadlineExceeded)
		return c, func() { c.cancel(true, Canceled) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
         // 设置退出时间,超时后 c 将从其 parent 的 children 中移除,可在此时间之前撤销此操作
		c.timer = time.AfterFunc(d, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
	return c, func() { c.cancel(true, Canceled) }
}

// 它通过停止 timer 来实现退出操作,然后将退出操作委托给 cancelCtx.cancel。
// It implements cancel by stopping its timer then delegating to cancelCtx.cancel.
type timerCtx struct {
	cancelCtx // timerCtx 具有 cancelCtx 的全部字段和方法
	timer *time.Timer // 从属于 cancelCtx.mu

	deadline time.Time
}

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

func (c *timerCtx) String() string {
	return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline))
}

func (c *timerCtx) cancel(removeFromParent bool, err error) {
     // c 的 parent 退出后,parent 的 child 也会退出
	c.cancelCtx.cancel(false, err)
	if removeFromParent {
		// 将 c 从其 parent 的 children 中移除
		removeChild(c.cancelCtx.Context, c)
	}
    // 如果有计时器,停止计时器
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// 返回带有键值对的 parent 的副本。
// context 值仅用于传输处理和 API 的请求域,而不是用于给函数传递可选参数。
// 提供的 key 必须是可比较的,并且不能是 string 等内置类型,以避免包与 context 之间的冲突。
// 使用此函数的用户应当为 key 定义用户自己的类型。
// 当分配给一个 interface{} 时,要避免重新分配内存,context 的 key 通常使用具体类型 struct{}。
// 或者,对外暴露的 context key 变量的静态类型应该是一个指针或 interface。
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}
}

// valueCtx 带有一个键值对。它实现了该键的值,并且将所有其它调用都委托给嵌套的 Context。
type valueCtx struct {
	Context
	key, val interface{}
}

func (c *valueCtx) String() string {
	return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}

func (c *valueCtx) Value(key interface{}) interface{} {
	if c.key == key {
		return c.val
	}
	return c.Context.Value(key)
}

LLLeon avatar Sep 14 '17 15:09 LLLeon