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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 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{} 然后逐个取消即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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 是否包含。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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 方法时创建(暂不考虑其他衍生类型),创建方法比较简单:

1
2
3
4
5
6
7
8
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,关于这两个方法的分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// 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,事实上官方也是怎么做的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 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 方法时有这么一段:

1
2
3
4
5
6
7
8
9
10
11
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 也没有任何意义。**

1
2
3
4
5
6
7
8
9
10
11
12
// 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 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,然后再把定时器停掉:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 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 的相关调用;我甚至觉得我有些地方可能理解的也不完全正确,目前就先写到这里,如果有不对的地方欢迎补充。


Golang Context 源码分析
https://mritd.com/2021/06/27/golang-context-source-code/
作者
Kovacs
发布于
2021年6月27日
许可协议