Golang中上下文切换之context包

Golang中用于控制goroutine的方式有很多,可以用channel阻塞控制,但是更普遍的方式是使用context上下文。

context的功能主要有:

  1. 超时控制
  2. 截止时间控制(本质也是超时)
  3. 手动退出
  4. 传值
  5. 控制所有的子协程,一起退出

创建方式

contextcontext包中,是以interface的形式出现的

1
2
3
4
5
6
type Context interface {
Deadline() (deadline time.Time, ok bool) // 截止日期返回代表此上下文完成的工作应取消的时间。未设置截止日期时,截止日期返回ok==false。连续调用Deadline返回相同的结果。
Done() <-chan struct{} // 返回一个只读的chan,当ctx取消时,通道关闭。同时,子ctx也会关闭。
Err() error // 如果Done没有close,返回nil。如果Done已close,Err将返回一个非零错误,说明原因:如果上下文已取消,则返回Canceled;如果上下文的截止日期已过,则返回DeadlineExceeded。Err返回非零错误后,对Err的连续调用将返回相同的错误。
Value(key any) any // 返回存储在ctx中的值
}

创建ctx的方式以上面的几种功能为主

1
2
3
4
5
6
func Background() Context {}     // 返回非零的空上下文。从不取消,没有value,也没有deadline。通常用于主函数、初始化和测试,并作为传入请求的root ctx。
func TODO() Context {} // TODO返回非零的空上下文。代码使用TODO的情况:当不清楚要使用哪个ctx或它还不可用时(因为周围函数尚未扩展为接受ctx参数)。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {} // WithCancel返回带有Done通道的父级副本
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {} // WithCancel返回带有Done通道的父级副本,Deadline时刻会取消,可手动执行CancelFunc
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {} // WithTimeout返回带有Done通道的父级副本,Timeout后会取消,可手动执行CancelFunc
func WithValue(parent Context, key, val any) Context {} // WithValue返回父项的副本,其中与键关联的值为val。

从创建方式可以看到,ctx是从父ctx衍生出来的子ctx

preview

emptyCtx

ctx

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

方法中,全部返回nil,因此,emptyCtx其实是一个 “啥也不干” 的Context它通常用于创建 root Context,标准库中 context.Background()context.TODO() 返回的就是这个 emptyCtx

1
2
3
4
5
6
7
func Background() Context {
return background
}

func TODO() Context {
return todo
}

cancelCtx

带有取消功能的ctx,可以取消ctx。取消时,它还会取消所有的子ctx

1
2
3
4
5
6
7
8
type cancelCtx struct {
Context // 一个接口实例,父ctx

mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call // 使用原子操作,存放 chan struct{}
children map[canceler]struct{} // set to nil by the first cancel call 存储子ctx
err error // set to non-nil by the first cancel call
}

ctx从父ctx中创建而来,Context存储父ctx

当此ctx创建子ctx,则将子ctx存储到children中。取消时,便利children中的子ctx逐个取消。

创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
// 设置父ctx
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}

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

包含的方法

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
func (c *cancelCtx) Value(key any) any {
// 判断获取的key是否是cancelCtxKey的标记,如果是标记,则返回自身cancelCtx
if key == &cancelCtxKey {
return c
}
// 如果查询的不是类型标记,则是取对应key的值
return value(c.Context, key)
}

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

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.
// close c.done,取消c的每个子级,如果removeFromParent为true,则从其父级的子级中删除c。
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
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan)
} else {
close(d)
}
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)
}
}

timerCtx

带有计时器和截止时间的ctx,可以在特定时间自动取消。通过*time.Timertime.Time实现

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

deadline time.Time
}

创建方式

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
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
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) }
}

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

包含的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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()
}

valueCtx

包含键值对的ctx,可以存储键值对,也可以读取键值对

数据结构

1
2
3
4
type valueCtx struct {
Context
key, val any
}

创建方式

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

包含的方法

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
func stringify(v any) 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) + ")"
}

// 如果ctx没有key,则读取父ctx
func (c *valueCtx) Value(key any) any {
if c.key == key {
return c.val
}
return value(c.Context, key)
}

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

源码

根ctx

根ctx有两个,backgroundtodo,都是一个指向emptyCtx的指针

1
2
3
4
5
6
type emptyCtx int

var (
background = new(emptyCtx)
todo = new(emptyCtx)
)

emptyCtx实现Context接口的方法都是返回nil,无法取消,没有存储键值对,也不会返回错误。

cancelCtx

创建过程

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

func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
1
2
3
4
5
6
7
8
type cancelCtx struct {
Context // 一个接口实例,父ctx

mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call // 使用原子操作,存放 chan struct{}
children map[canceler]struct{} // set to nil by the first cancel call 存储子ctx
err error // set to non-nil by the first cancel call
}

创建过程,其实是将父ctx,放到ctx结构体中,其中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
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}

// 传递取消
func propagateCancel(parent Context, child canceler) {
// 当从根ctx衍生出来的ctx,则done为nil,例如上图中的ctx2
done := parent.Done()
if done == nil {
return // parent is never canceled
}
// 对于其他的标准的可取消的 ctx 调用 Done() 方法将会延迟初始化 done channel(调用时创建)
// 所以 done channel 为 nil 时说明 parent context 必然永远不会被取消,所以就无需及联到 child Context。
// 例如父ctx是根ctx,就不需要关联根ctx的子ctx是自身

// 例如ctx5,父ctx的done不为nil
select {
case <-done:
// parent is already canceled , 父ctx已经取消,子ctx则应该立即取消
// 当从一个取消的ctx派生子ctx,则该子ctx初始化完就cancel了,这里使用false,是因为没有加到父ctx的map中
child.cancel(false, parent.Err())
return
default:
}

// 判断父ctx的类型是否是cancelCtx。这里的类型判断,是通过key存放标记实现。
// ok代表父parent是标准的cancelCtx类型,并且还没有cancel掉
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock() // 加锁,防止cancelCtx被并发修改,后续会有对map的操作,需要加锁
if p.err != nil {
// parent has already been canceled
// 父ctx已经cancel了,直接cancel本ctx
// 这里为false的原因,是值不需要从父ctx的map中删除自己,因为父ctx在cancel的的时候已经删掉了,避免重复删除
child.cancel(false, p.err)
} else {
// 父ctx没有cancel,则初始化父ctx中的map中
// 这个map在后续遍历中调用cancel方法
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 父ctx根本无法取消或者无法转换为一个*cancelCtx,例如父ctx是一个withValue的ctx,不是*cancelCtx
// 这个逻辑是指无法通过map关联父ctx和ctx的关系,因此,通过全局goroutines存储个数
// 并且开启协程监听父ctx是否完成,完成,则执行该ctx的cancel,而且不需要在父ctx的map中删除自身,因为本身就没有记录在父ctx的map中
// 或者监听该ctx自身Done
atomic.AddInt32(&goroutines, +1)
go func() {
select {
case <-parent.Done():
// 取消子ctx,并且沿用父ctx的报错
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}

// parentCancelCtx返回父ctx的*cancelCtx。
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
// 判断父ctx是否已经关闭,或者是否是空
// 如果done已经关闭,返回这个ctx没有意义,因为父ctx关闭,子ctx都会关闭
// 如果done为空,则说明无法cancel,统一返回nil
if done == closedchan || done == nil {
return nil, false
}
// 类型断言,判断是否是cancelCtx类型
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
// 如果是cancelCtx类型,通过原子操作Load出chan struct{}
pdone, _ := p.done.Load().(chan struct{})
// 判断获取的chan,是否与parent.Done()的chan是一样的。
// 如果是一致的,说明Done方法没有被重写,是原生的cancelCtx timerCtx valueCtx,可以正常执行cancel逻辑
// 如果不是一直的,说明父ctx是一个重写的cancelCtx,并且重写了Done方法,没有返回标准的*cancelCtx
if pdone != done {
return nil, false
}
return p, true
}

// cancel函数在取消过程中有详细说明
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
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan)
} else {
close(d)
}
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)
}
}

// closedchan是一个可重复使用的封闭通道。
var closedchan = make(chan struct{})

func init() {
close(closedchan)
}

取消过程

取消是通过一个Done channel实现,要取消这个context,则需要让所有c.Done()停止阻塞,要么是close这个channel,要么是用一个closedchannel替换

源码

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
// 如果removeFromParent为true,则从其父ctx的子级中删除ctx。
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")
}
// 加锁,中途会对map进行遍历和更改
c.mu.Lock()
if c.err != nil {
// 说明这个ctx已经cancel了
c.mu.Unlock()
return // already canceled
}
// 没有cancel时,将父ctx的错误赋值给本ctx
c.err = err
// 原子操作,类型判断,判断chan是否是nil
d, _ := c.done.Load().(chan struct{})
// 如果是nil,则用一个已经关闭的channel替换
// 创建子ctx的时候,没有store,没有初始化,因此这个可能是nil
if d == nil {
c.done.Store(closedchan)
} else {
// 如果不是nil,已经初始化,则关闭这个channel
close(d)
}
// 便利这个ctx的子ctx,清除子ctx
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
// 在持有父锁的同时获取子锁。
// 此时是从父ctx中删除子ctx,所以不需要再次获取这个子ctx的父ctx,在父ctx的map中统一删除
child.cancel(false, err)
}
// 清楚父ctx的map,也就不用上面cancel传入true一个一个删
c.children = nil
// 解锁
c.mu.Unlock()

// 如果 removeFromParent 为true,则从父ctx中清理自己
if removeFromParent {
removeChild(c.Context, c)
}
}

// 从父ctx的map中删除自身
// 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()
}

默认从父ctx中清除都是false,为什么不是true

true的情况指的是,调用WithCancel()生成的cancel func

1
2
3
4
5
6
7
8
9
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, &c)
// cancel这个函数,在调用的时候,会执行cancel,需要传入true
return &c, func() { c.cancel(true, Canceled) }
}

img

直白点说,就是当本ctx cancel时,除了清理子ctx,还需要与父ctx断绝关系。

timerCtx

timerCtx可以通过WithDeadlineWithTimeout创建出来

创建过程

WithTimeout其实是用WithDeadline创建出来的,直接看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
51
52
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) {
// 检查父ctx是否为nil
if parent == nil {
panic("cannot create context from nil parent")
}
// 假设父ctx的截止时间早于当前设置的截止时间,那就意味着父ctx肯定会先被 cancel,
// 同样由于父ctx的 cancel 会导致当前这个ctx也会被 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{
// cancel ctx
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 传播cancel,与cancelCtx一致
propagateCancel(parent, c)
// 判断当前时间是否已经过了截止时间
dur := time.Until(d)
if dur <= 0 {
// 截止时间已过,自己cancle,并且从父ctx的map中清除自己
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
// 没有过截止时间,所有的判断都check了,加锁
c.mu.Lock()
defer c.mu.Unlock()
// 多做一层逻辑判断,懒汉模式,以防并发下到这里cancel了
if c.err == nil {
// 设置一个定机器
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
// 返回,同时也返回随时取消
return c, func() { c.cancel(true, Canceled) }
}

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

func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}

取消过程

指手动cancel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (c *timerCtx) cancel(removeFromParent bool, err error) {
// 执行cancel
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
// 从父ctx中删除自身
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
// 停掉计时器
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}

valueCtx

1
2
3
4
type valueCtx struct {
Context
key, val any
}

创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func WithValue(parent Context, key, val any) Context {
// 判断父ctx
if parent == nil {
panic("cannot create context from nil parent")
}
// key不能为nil
if key == nil {
panic("nil key")
}
// 判断key是否可比较,排除map、slice
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}

相比较而言,valueCtx就很简单,存储键值即可。

但是可以看到,结构体中存放的是单一的键值,而不是map slice之类的多指

获取值

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
func (c *valueCtx) Value(key any) any {
// 判断key是否一致
if c.key == key {
return c.val
}
// 查询父ctx
return value(c.Context, key)
}

func value(c Context, key any) any {
// 循环遍历父ctx
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)
}
}
}

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

img

和链表有点像,只是它的方向相反:Context 指向它的父节点,链表则指向下一个节点。通过 WithValue 函数,可以创建层层的 valueCtx,存储 goroutine 间可以共享的变量。

它会顺着链路一直往上找,比较当前节点的 key 是否是要找的 key,如果是,则直接返回 value。否则,一直顺着 context 往前,最终找到根节点(一般是 emptyCtx),直接返回一个 nil。所以用 Value 方法的时候要判断结果是否为 nil。

因为查找方向是往上走的,所以,父节点没法获取子节点存储的值,子节点却可以获取父节点的值。

如何使用context

context 会在函数传递间传递。只需要在适当的时间调用 cancel 函数向 goroutines 发出取消信号或者调用 Value 函数取出 context 中的值。

在官方博客里,对于使用 context 提出了几点建议:

  1. 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
  2. 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
  3. 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
  4. 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。

有争议的其实是WithValue的用法

  1. 适合于请求绑定,而不是于数据绑定。例如token,reqest_id
  2. 防止滥用