这篇文章讲的太好了,自己记录一下加深印象

  • 主要用途:用于在异步场景并发协调以及对goruntinue的生命周期进行控制,传递取消信号,超时,截止时间,并且具有一定的数据存储能力

核心数据结构

1
2
3
4
5
6
type Context interface {
Deadline() (deadline time.Time, ok bool)//返回context的过期时间
Done() <-chan struct{}//返回一个channel,当Context被取消或超时,这个channel会关闭
Err() error//返回错误
Value(key any) any//在调用链中携带的键值对数据
}

emptyCtx

根基:是所有context的根

  • 类的实现
1
2
3
4
5
6
7
8
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 any) any {return}

常用的context.BackGround()与context.TODO()方法返回的都是emptyCtx

cancelCtx

1
2
3
4
5
6
7
8
9
10
11
type cancelCtx struct {
Context
mu sync.Mutex
done atomic.Value
children map[canceler]struct{}
err error
}
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}

内嵌了一个context,所以可见cancelCtx必定是某个context的子context
children: 此cancelCtx的子context

  • Deadline:cancelCtx并没有实现这个方法,只是内嵌了有Deadline()方法的Context,直接调用会报错
  • Done:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (c *cancelCtx) Done() <-chan struct{} {
//第一次Load,没有这一步,上来就加锁,成千上万个协程都要去抢锁,性能会很差
d := c.done.Load()
if d != nil {
//有值直接返回
return d.(chan struct{})
}
c.mu.Lock()
defer c.mu.Unlock()
d = c.done.Load()
//为什么还要检查?确保channel只被创建一次
//协程A,B同时调用Done(),第一次检查AB都发现d为nil,去抢锁,A拿到锁进去创建channel,然后解锁,此时b拿到锁,如果不Load()的话,B以为还是nil,会再创建一个channel把A建的覆盖
if d == nil {
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
}
  • Err
    1
    2
    3
    4
    5
    6
     func (c *cancelCtx) Err() error {
    c.mu.Lock()
    err := c.err
    c.mu.Unlock()
    return err
    }
  • Value
    1
    2
    3
    4
    5
    6
     func (c *cancelCtx) Value(key any) any {
    if key == &cancelCtxKey {
    return c
    }
    return value(c.Context, key)
    }

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) }
}

当我们调用 WithCancel 创建它时,它不仅初始化了自己,还必须立刻寻找上级。使用propagateCancel,尝试将新节点加入到父节点的 children 列表中;如果父节点不支持列表(非标准实现),它就启动一个守护协程来通过 Channel 监听父节点的生死。

propagateCancel
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
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
if done == nil {
return // parent is never canceled
}

select {
case <-done:
// parent is already canceled
child.cancel(false, parent.Err())
return
default:
}

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():
}
}()
}
}

timerCtx

1
2
3
4
5
6
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.

deadline time.Time
}

它在cancel的基础上再进行封装,增加了一个time.Timer用于定时终止context,deadline用于timerCtx的过期时间

timerCtx.cancel
context.WithTimeout & context.WithDeadline

valueCtx

1
2
3
4
type valueCtx struct {
Context
key, val any
}
valueCtx.Value()
1
2
3
4
5
6
func (c *valueCtx) Value(key any) any {
if c.key == key {
return c.val
}
return value(c.Context, key)
}

调用了value内部方法

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
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
case *timerCtx:
if key == &cancelCtxKey {
return &ctx.cancelCtx
}
c = ctx.Context
case *emptyCtx:
return nil
default:
return c.Value(key)
}
}
}
valueCtx.WithValue()
1
2
3
4
5
6
7
8
9
10
11
12
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}