Golang Context 源码分析

本文所有源码分析基于 Go 1.16.4,阅读时请自行切换版本。

一、Context 介绍

标准库中的 Context 是一个接口,其具体实现有很多种;Context 在 Go 1.7 中被添加入标准库,主要用于跨多个 Goroutine 设置截止时间、同步信号、传递上下文请求值等。

由于需要跨多个 Goroutine 传递信号,所以多个 Context 往往需要关联到一起,形成类似一个树的结构。这种树状的关联关系需要有一个根(root) Context,然后其他 Context 关联到 root Context 成为它的子(child) Context;这种关联可以是多级的,所以在角色上 Context 分为三种:

  • root(根) Context
  • parent(父) Context
  • child(子) Context

二、Context 类型

2.1、Context 的创建

标准库中定义的 Context 创建方法大致如下:

  • context.Background(): 该方法用于创建 root Context,且不可取消
  • context.TODO(): 该方法同样用于创建 root Context(不准确),也不可取消,TODO 通常代表不知道要使用哪个 Context,所以后面可能有调整
  • context.WithCancel(parent Context): 从 parent Context 创建一个带有取消方法的 child Context,该 Context 可以手动调用 cancel
  • context.WithDeadline(parent Context, d time.Time): 从 parent Context 创建一个带有取消方法的 child Context,不同的是当到达 d 时间后该 Context 将自动取消
  • context.WithTimeout(parent Context, timeout time.Duration): 与 WithDeadline 类似,只不过指定的是一个从当前时间开始的超时时间
  • context.WithValue(parent Context, key, val interface{}): 从 parent Context 创建一个 child Context,该 Context 可以存储一个键值对,同时这是一个不可取消的 Context

2.2、Context 内部类型

在阅读源码后会发现,Context 各种创建方法其实主要只使用到了 4 种类型的 Context 实现:

2.2.1、emptyCtx

emptyCtx 实际上就是个 int,其对 Context 接口的主要实现(DeadlineDoneErrValue)全部返回了 nil,也就是说其实是一个 “啥也不干” 的 Context;它通常用于创建 root Context,标准库中 context.Background()context.TODO() 返回的就是这个 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)
)

// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
	return background
}

// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
	return todo
}

2.2.2、cancelCtx

cancelCtx 内部包含一个 Context 接口实例,还有一个 children map[canceler]struct{};这两个变量的作用就是保证 cancelCtx 可以在 parent Context 和 child Context 两种角色之间转换:

  • 作为其他 Context 实例的 parent Context 时,将其他 Context 实例存储在 children map[canceler]struct{} 中建立关联关系
  • 作为其他 Context 实例的 child Context 时,将其他 Context 实例存储在 “Context” 变量里建立关联

cancelCtx 被定义为一个可以取消的 Context,而由于 Context 的树形结构,当作为 parent Context 取消时需要同步取消节点下所有 child Context,这时候只需要遍历 children map[canceler]struct{} 然后逐个取消即可。

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
	Context

	mu       sync.Mutex            // protects following fields
	done     chan struct{}         // created lazily, closed by first cancel call
	children map[canceler]struct{} // set to nil by the first cancel call
	err      error                 // set to non-nil by the first cancel call
}

func (c *cancelCtx) Value(key interface{}) interface{} {
	if key == &cancelCtxKey {
		return c
	}
	return c.Context.Value(key)
}

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()
	err := c.err
	c.mu.Unlock()
	return err
}

type stringer interface {
	String() string
}

func contextName(c Context) string {
	if s, ok := c.(stringer); ok {
		return s.String()
	}
	return reflectlite.TypeOf(c).String()
}

func (c *cancelCtx) String() string {
	return contextName(c.Context) + ".WithCancel"
}

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's 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 // already canceled
	}
	c.err = err
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done)
	}
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

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

2.2.3、timerCtx

timerCtx 实际上是在 cancelCtx 之上构建的,唯一的区别就是增加了计时器和截止时间;有了这两个配置以后就可以在特定时间进行自动取消,WithDeadline(parent Context, d time.Time)WithTimeout(parent Context, timeout time.Duration) 方法返回的都是这个 timerCtx

// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
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) String() string {
	return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
		c.deadline.String() + " [" +
		time.Until(c.deadline).String() + "])"
}

func (c *timerCtx) cancel(removeFromParent bool, err error) {
	c.cancelCtx.cancel(false, err)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

2.2.4、valueCtx

valueCtx 内部同样包含了一个 Context 接口实例,目的也是可以作为 child Context,同时为了保证其 “Value” 特性,其内部包含了两个无限制变量 key, val interface{}在调用 valueCtx.Value(key interface{}) 会进行递归向上查找,但是这个查找只负责查找 “直系” Context,也就是说可以无限递归查找 parent Context 是否包含这个 key,但是无法查找兄弟 Context 是否包含。

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
	Context
	key, val interface{}
}

// stringify tries a bit to stringify v, without using fmt, since we don't
// want context depending on the unicode tables. This is only used by
// *valueCtx.String().
func stringify(v interface{}) string {
	switch s := v.(type) {
	case stringer:
		return s.String()
	case string:
		return s
	}
	return "<not Stringer>"
}

func (c *valueCtx) String() string {
	return contextName(c.Context) + ".WithValue(type " +
		reflectlite.TypeOf(c.key).String() +
		", val " + stringify(c.val) + ")"
}

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

三、cancelCtx 源码分析

3.1、cancelCtx 是如何被创建的

cancelCtx 在调用 context.WithCancel 方法时创建(暂不考虑其他衍生类型),创建方法比较简单:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

newCancelCtx 方法就是将 parent Context 设置到内部变量中,值得分析的是 propagateCancel(parent, &c) 方法和被其调用的 parentCancelCtx(parent Context) (*cancelCtx, bool) 方法,这两个方法保证了 Context 链可以从顶端到底端的及联 cancel,关于这两个方法的分析如下:

// propagateCancel arranges for child to be canceled when parent is.
// propagateCancel 这个方法主要负责保证当 parent Context 被取消时,child Context 也会被及联取消
func propagateCancel(parent Context, child canceler) {
	// 针对于 context.Background()/TODO() 创建的 Context(emptyCtx),其 done channel 将永远为 nil
	// 对于其他的标准的可取消的 Context(cancelCtx、timerCtx) 调用 Done() 方法将会延迟初始化 done channel(调用时创建)
	// 所以 done channel 为 nil 时说明 parent context 必然永远不会被取消,所以就无需及联到 child Context
	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}

	// 如果 done channel 不是 nil,说明 parent Context 是一个可以取消的 Context
	// 这里需要立即判断一下 done channel 是否可读取,如果可以读取说明上面无锁阶段
	// parent Context 已经被取消了,那么应该立即取消 child Context
	select {
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}

	// parentCancelCtx 用于获取 parent Context 的底层可取消 Context(cancelCtx)
	//
	// 如果 parent Context 本身就是 *cancelCtx 或者是标准库中基于 cancelCtx 衍生的 Context 会返回 true
	// 如果 parent Context 已经取消/或者根本无法取消 会返回 false
	// 如果 parent Context 无法转换为一个 *cancelCtx 也会返回 false
	// 如果 parent Context 是一个自定义深度包装的 cancelCtx(自己定义了 done channel) 则也会返回 false
	if p, ok := parentCancelCtx(parent); ok { // ok 为 true 说明 parent Context 为 标准库的 cancelCtx 或者至少可以完全转换为 *cancelCtx
		// 先对 parent Context 加锁,防止更改
		p.mu.Lock()
		// 因为 ok 为 true 就已经确定了 parent Context 一定为 *cancelCtx,而 cancelCtx 取消时必然设置 err
		// 所以并发加锁情况下如果 parent Context 的 err 不为空说明已经被取消了
		if p.err != nil {
			// parent has already been canceled
			// parent Context 已经被取消,则直接及联取消 child Context
			child.cancel(false, p.err)
		} else {
			// 在 ok 为 true 时确定了 parent Context 一定为 *cancelCtx,此时 err 为 nil
			// 这说明 parent Context 还没被取消,这时候要在 parent Context 的 children map 中关联 child Context
			// 这个 children map 在 parent Context 被取消时会被遍历然后批量调用 child Context 的取消方法
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else { // ok 为 false,说明: "parent Context 已经取消" 或 "根本无法取消" 或 "无法转换为一个 *cancelCtx" 或 "是一个自定义深度包装的 cancelCtx"
		atomic.AddInt32(&goroutines, +1)
		// 由于代码在方法开始时就判断了 parent Context "已经取消"、"根本无法取消" 这两种情况
		// 所以这两种情况在这里不会发生,因此 <-parent.Done() 不会产生 panic
		// 
		// 唯一剩下的可能就是 parent Context "无法转换为一个 *cancelCtx" 或 "是一个被覆盖了 done channel 的自定义 cancelCtx"
		// 这种两种情况下无法通过 parent Context 的 children map 建立关联,只能通过创建一个 Goroutine 来完成及联取消的操作
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

// parentCancelCtx returns the underlying *cancelCtx for parent.
// It does this by looking up parent.Value(&cancelCtxKey) to find
// the innermost enclosing *cancelCtx and then checking whether
// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
// parentCancelCtx 负责从 parent Context 中取出底层的 cancelCtx
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	// 如果 parent context 的 done 为 nil 说明不支持 cancel,那么就不可能是 cancelCtx
	// 如果 parent context 的 done 为 可复用的 closedchan 说明 parent context 已经 cancel 了
	// 此时取出 cancelCtx 没有意义(具体为啥没意义后面章节会有分析)
	done := parent.Done()
	if done == closedchan || done == nil {
		return nil, false
	}

	// 如果 parent context 属于原生的 *cancelCtx 或衍生类型(timerCtx) 需要继续进行后续判断
	// 如果 parent context 无法转换到 *cancelCtx,则认为非 cancelCtx,返回 nil,fasle
	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
	if !ok {
		return nil, false
	}
	p.mu.Lock()
	// 经过上面的判断后,说明 parent context 可以被转换为 *cancelCtx,这时存在多种情况:
	//   - parent context 就是 *cancelCtx
	//   - parent context 是标准库中的 timerCtx
	//   - parent context 是个自己自定义包装的 cancelCtx
	//
	// 针对这 3 种情况需要进行判断,判断方法就是: 
	//   判断 parent context 通过 Done() 方法获取的 done channel 与 Value 查找到的 context 的 done channel 是否一致
	// 
	// 一致情况说明 parent context 为 cancelCtx 或 timerCtx 或 自定义的 cancelCtx 且未重写 Done(),
	// 这种情况下可以认为拿到了底层的 *cancelCtx
	// 
	// 不一致情况说明 parent context 是一个自定义的 cancelCtx 且重写了 Done() 方法,并且并未返回标准 *cancelCtx 的
	// 的 done channel,这种情况需要单独处理,故返回 nil, false
	ok = p.done == done
	p.mu.Unlock()
	if !ok {
		return nil, false
	}
	return p, true
}

3.2、cancelCtx 是如何取消的

在上面的 cancelCtx 创建源码中可以看到,cancelCtx 内部跨多个 Goroutine 实现信号传递其实靠的就是一个 done channel;如果要取消这个 Context,那么就需要让所有 <-c.Done() 停止阻塞,这时候最简单的办法就是把这个 channel 直接 close 掉,或者干脆换成一个已经被 close 的 channel,事实上官方也是怎么做的。

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
    // 首先判断 err 是不是 nil,如果不是 nil 则直接 panic
    // 这么做的目的是因为 cancel 方法是个私有方法,标准库内任何调用 cancel
    // 的方法保证了一定会传入 err,如果没传那就是非正常调用,所以可以直接 panic
	if err == nil {
		panic("context: internal error: missing cancel error")
	}
	// 对 context 加锁,防止并发更改
	c.mu.Lock()
	// 如果加锁后有并发访问,那么二次判断 err 可以防止重复 cancel 调用
	if c.err != nil {
		c.mu.Unlock()
		return // already canceled
	}
	// 这里设置了内部的 err,所以上面的判断 c.err != nil 与这里是对应的
	// 也就是说加锁后一定有一个 Goroutine 先 cannel,cannel 后 c.err 一定不为 nil
	c.err = err
	// 判断内部的 done channel 是不是为 nil,因为在 context.WithCancel 创建 cancelCtx 的
	// 时候并未立即初始化 done channel(延迟初始化),所以这里可能为 nil
	// 如果 done channel 为 nil,那么就把它设置成共享可重用的一个已经被关闭的 channel
	if c.done == nil {
		c.done = closedchan
	} else { // 如果 done channel 已经被初始化,则直接 close 它
		close(c.done)
	}
	// 如果当前 Context 下面还有关联的 child Context,且这些 child Context 都是
	// 可以转换成 *cancelCtx 的 Context(见上面的 propagateCancel 方法分析),那么
	// 直接遍历 childre map,并且调用 child Context 的 cancel 即可
	// 如果关联的 child Context 不能转换成 *cancelCtx,那么由 propagateCancel 方法
	// 中已经创建了单独的 Goroutine 来关闭这些 child Context
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}
	// 清除 c.children map 并解锁
	c.children = nil
	c.mu.Unlock()

    // 如果 removeFromParent 为 true,那么从 parent Context 中清理掉自己
	if removeFromParent {
		removeChild(c.Context, c)
	}
}

3.3、parentCancelCtx 为什么不取出已取消的 cancelCtx

在上面的 3.1 章节中分析 parentCancelCtx 方法时有这么一段:

func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	// 如果 parent context 的 done 为 nil 说明不支持 cancel,那么就不可能是 cancelCtx
	// 如果 parent context 的 done 为 可复用的 closedchan 说明 parent context 已经 cancel 了
	// 此时取出 cancelCtx 没有意义(具体为啥没意义后面章节会有分析)
	done := parent.Done()
	if done == closedchan || done == nil {
		return nil, false
	}
	
	// ...... 省略
}

现在来仔细说明一下 “为什么没有意义?” 这个问题:

首先是调用 parentCancelCtx 方法的位置,在 context 包中只有两个位置调用了 parentCancelCtx 方法;一个是在创建 cancelCtx 的 func WithCancel(parent Context)propagateCancel(parent, &c) 方法中,另一个就是 cancel 方法的 removeChild(c.Context, c) 调用中;下面分析一下这两个方法的目的。

3.3.1、propagateCancel(parent, &c)

propagateCancel 负责保证当 parent cancelCtx 在取消时能正确传递到 child Context;那么它需要通过 parentCancelCtx 来确定 parent Context 是否是一个 cancelCtx,如果是那就把 child Context 加到 parent Context 的 children map 中,然后 parent Context 在 cancel 时会自动遍历 map 调用 child Context 的 cancel;如果不是那就开 Goroutine 阻塞读 parent Context 的 done channel然后再调用 child Context 的 cancel。

if p, ok := parentCancelCtx(parent); ok {
    p.mu.Lock()
    if p.err != nil {
        // parent has already been canceled
        child.cancel(false, p.err)
    } else {
        if p.children == nil {
            p.children = make(map[canceler]struct{})
        }
        p.children[child] = struct{}{}
    }
    p.mu.Unlock()
} else {
    atomic.AddInt32(&goroutines, +1)
    go func() {
        select {
        case <-parent.Done():
            child.cancel(false, parent.Err())
        case <-child.Done():
        }
    }()
}

所以在这个方法调用时,如果 parentCancelCtx 取出一个已取消的 cancelCtx,那么 parent Context 的 children map 在 cancel 时已经清空了,这时要是再给设置上就有问题了,同样业务需求中 propagateCancel 为了就是控制传播,明明 parent Context 已经 cancel 了,再去传播就没意义了。

3.3.2、removeChild(c.Context, c)

同上面的 3.3.1 一样,**removeChild(c.Context, c) 目的是在 cancel 时断开与 parent Context 的关联,同样是为了处理 children map 的问题;此时如果 parentCancelCtx 也取出一个已经 cancel 的 parent Context,由于 parent Context 在 cancel 时已经清空了 childre map,这里再尝试 remove 也没有任何意义。**

// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {
	p, ok := parentCancelCtx(parent)
	if !ok {
		return
	}
	p.mu.Lock()
	if p.children != nil {
		delete(p.children, child)
	}
	p.mu.Unlock()
}

四、timerCtx 源码分析

4.1、timerCtx 是如何创建的

timerCtx 的创建主要通过 context.WithDeadline 方法,同时 context.WithTimeout 实际上也是调用的 context.WithDeadline:

// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
    // 与 cancelCtx 一样先检查一下 parent Context
	if parent == nil {
		panic("cannot create context from nil parent")
	}
    
    // 判断 parent Context 是否支持 Deadline,如果支持的话需要判断 parent Context 的截止时间
    // 假设 parent Context 的截止时间早于当前设置的截止时间,那就意味着 parent Context 肯定会先
    // 被 cancel,同样由于 parent Context 的 cancel 会导致当前这个 child Context 也会被 cancel
    // 所以这时候直接返回一个 cancelCtx 就行了,计时器已经没有必要存在了
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}

    // 创建一个 timerCtx
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

    // 与 cancelCtx 一样的传播操作
	propagateCancel(parent, c)

    // 判断当前时间已经已经过了截止日期,如果超过了直接 cancel
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded) // deadline has already passed
		return c, func() { c.cancel(false, Canceled) }
	}

    // 所有 check 都没问题的情况下,创建一个定时器,在到时间后自动 cancel
	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) }
}

4.2、timerCtx 是如何取消的

了解了 cancelCtx 的取消流程以后再来看 timerCtx 的取消就相对简单的多,主要就是调用一下里面的 cancelCtx 的 cancel,然后再把定时器停掉:

func (c *timerCtx) cancel(removeFromParent bool, err error) {
	c.cancelCtx.cancel(false, err)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

五、valueCtx 源码分析

相对于 cancelCtx 还有 timerCtx,valueCtx 实在是过于简单,因为它没有及联的取消逻辑,也没有过于复杂的 kv 存储:

// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using context. Users of WithValue should define their own
// types for keys. To avoid allocating when assigning to an
// interface{}, context keys often have concrete type
// struct{}. Alternatively, exported context key variables' static
// type should be a pointer or interface.
// WithValue 方法负责创建 valueCtx
func WithValue(parent Context, key, val interface{}) Context {
    // parent 检测
	if parent == nil {
		panic("cannot create context from nil parent")
	}
    // key 检测
	if key == nil {
		panic("nil key")
	}
    // key 必须是可比较的
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
	Context
	key, val interface{}
}

// stringify tries a bit to stringify v, without using fmt, since we don't
// want context depending on the unicode tables. This is only used by
// *valueCtx.String().
func stringify(v interface{}) string {
	switch s := v.(type) {
	case stringer:
		return s.String()
	case string:
		return s
	}
	return "<not Stringer>"
}

func (c *valueCtx) String() string {
	return contextName(c.Context) + ".WithValue(type " +
		reflectlite.TypeOf(c.key).String() +
		", val " + stringify(c.val) + ")"
}

func (c *valueCtx) Value(key interface{}) interface{} {
    // 先判断当前 Context 里有没有这个 key
	if c.key == key {
		return c.val
	}
    // 如果没有递归向上查找
	return c.Context.Value(key)
}

六、结尾

分析 Context 源码断断续续经历了 3、4 天,说心里话发现里面复杂情况有很多,网上其他文章很多都是只提了一嘴,但是没有深入具体逻辑,尤其是 cancelCtx 的相关调用;我甚至觉得我有些地方可能理解的也不完全正确,目前就先写到这里,如果有不对的地方欢迎补充。


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 国际许可协议进行许可,转载请注明出处。