特别提醒,本文所涉及的源码是go1.21.0 darwin/amd64
1. 什么是Context 在多个goroutine之间传递上下文的对象,传递的信息包括取消信号、截止时间以及其他一些跨api边界的值
2. 核心数据结构 2.1 Context 1 2 3 4 5 6 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key any) any }
Context
定义为Interface
,定义了4个核心函数:
Dealine:返回context
的过期时间和一个bool
值判断是否设置了deadline
Done:返回context
中的channel
,该channel
会在当前工作完成或者上下文被取消后关闭,多次调用返回的是同一个channel
,如果该context
是不能被取消的,则会返回nil
Err:返回context
结束的原因
Value:返回contxet
中存储的key对应的值
2.2 error 1 2 3 4 5 6 7 8 9 var Canceled = errors.New("context canceled" )var DeadlineExceeded error = deadlineExceededError{}type deadlineExceededError struct {}func (deadlineExceededError) Error() string { return "context deadline exceeded" }func (deadlineExceededError) Timeout() bool { return true }func (deadlineExceededError) Temporary() bool { return true }
Canceled:context
被cancel时会返回该类错误
DeadlineExceeded:context
超时时会返回该类错误
3 具体实现
3.1 emptyCtx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type emptyCtx struct {}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 }
emptyCtx
是一个空的context
,之前的一些版本中是直接将它定义为一个int
类型
3.2 backgroundCtx 1 2 3 4 5 6 7 8 9 type backgroundCtx struct { emptyCtx }func (backgroundCtx) String() string { return "context.Background" } func Background () Context { return backgroundCtx{} }
Background()
返回一个非nil但是为空的context
。它不能被取消,没有任何值,也没有截止时间。通常被主函数、初始化、测试以及作为传入请求的顶层context
使用。
3.3 todoCtx 1 2 3 4 5 6 7 8 9 type todoCtx struct { emptyCtx }func (todoCtx) String() string { return "context.TODO" } func TODO () Context { return todoCtx{} }
TODO()
同样返回一个非nil但是为空的context
。在代码不清楚应该使用哪个上下文或者上下文尚不可用时,应该使用context.TODO
3.4 cancelCtx 1 2 3 4 5 6 7 8 9 type cancelCtx struct { Context mu sync.Mutex done atomic.Value children map [canceler]struct {} err error cause error }
cancelCtx
是取消机制的关键
3.4.1 Value方法 1 2 3 4 5 6 7 8 9 var cancelCtxKey int func (c *cancelCtx) Value(key any) any { if key == &cancelCtxKey { return c } return value(c.Context, key) }
如果key
的值为&cancelCtxKey
,则返回cancelCtx
本身的指针,否则返回对应key
存储的value
值
3.4.2 Done方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 {}) }
前面提到done
属性是一个原子类型的值,因此通过atomic
包中的Load()
函数获取它的值,如果它已经存在则直接返回
如果不存在则先加锁,然后创建一个新的chan struct{}
,然后通过Store()
赋值给done
,可以看到这是一个懒加载机制,在第一次调用c.Done()
的时候,该属性才被创建,返回值,然后解锁
3.4.3 Err方法 1 2 3 4 5 6 func (c *cancelCtx) Err() error { c.mu.Lock() err := c.err c.mu.Unlock() return err }
加锁、读取值、解锁、返回结果
3.4.4 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 func (c *cancelCtx) propagateCancel(parent Context, child canceler) { c.Context = parent done := parent.Done() if done == nil { return } select { case <-done: child.cancel(false , parent.Err(), Cause(parent)) return default : } if p, ok := parentCancelCtx(parent); ok { p.mu.Lock() if p.err != nil { child.cancel(false , p.err, p.cause) } else { if p.children == nil { p.children = make (map [canceler]struct {}) } p.children[child] = struct {}{} } p.mu.Unlock() return } if a, ok := parent.(afterFuncer); ok { c.mu.Lock() stop := a.AfterFunc(func () { child.cancel(false , parent.Err(), Cause(parent)) }) c.Context = stopCtx{ Context: parent, stop: stop, } c.mu.Unlock() return } goroutines.Add(1 ) go func () { select { case <-parent.Done(): child.cancel(false , parent.Err(), Cause(parent)) case <-child.Done(): } }() }
该方法比较关键,会构建父子上下文之间的关联,当父上下文被取消时,子上下文也会被取消,不会出现状态不一致的情况
首先将parent
赋值给Context
属性
如果父上下文不会取消,则直接返回
如果父上下文已经取消,则直接终止子上下文
如果父上下文是可以取消的上下文类型,即cancelCtx
,则先加锁、然后将其加入到父上下文的children
中,最后解锁
如果父上下文实现了AfterFunc
方法,则在上下文被取消后,将子上下文也取消,通过调用一个stop()
函数来实现的
如果都没满足前面的条件,则启动一个协程监控parent状态,倘若父上下文终止,则终止子上下文
进一步观察parentCancelCtx
是如何校验parent
是否为cancelCtx
类型的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var closedchan = make (chan struct {})func init () { close (closedchan) } func parentCancelCtx (parent Context) (*cancelCtx, bool ) { done := parent.Done() if done == closedchan || done == nil { return nil , false } p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) if !ok { return nil , false } pdone, _ := p.done.Load().(chan struct {}) if pdone != done { return nil , false } return p, true }
closedchan
表示一个已经关闭的通道
倘若parent
的channel
已经被关闭或者是不会被cancel
的类型,则直接返回false
倘若用&cancelCtxKey
能取到值并且得到的值是parent
本身,返回true
(cancelCtx
的约定,key
为&cancelCtxKey
的时候,返回的是cancelCtx
本身的指针)
3.4.5 String方法 1 2 3 func (c *cancelCtx) String() string { return contextName(c.Context) + ".WithCancel" }
返回cancelCtx
的名字
3.4.6 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 func (c *cancelCtx) cancel(removeFromParent bool , err, cause error ) { if err == nil { panic ("context: internal error: missing cancel error" ) } if cause == nil { cause = err } c.mu.Lock() if c.err != nil { c.mu.Unlock() return } c.err = err c.cause = cause d, _ := c.done.Load().(chan struct {}) if d == nil { c.done.Store(closedchan) } else { close (d) } for child := range c.children { child.cancel(false , err, cause) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }
该方法接收三个参数,removeFromParent
是一个bool
值,表示需要从当前context
的父context
的children
中删除
加锁,判断自己的err
是否为空,如果不为空,说明已经被cancel
,直接返回
处理 cancelCtx
的 channel
,若channel
此前未初始化,则直接注入一个 closedChan
,否则关闭该 channel
遍历该cancelCtx
的子context
,依次将其cancel
解锁,最后根据传入的参数removeFromParent
,判断是否需要手动把 cancelCtx
从 parent
的children
集合中移除
走进 removeChild
方法中,观察如何将 cancelCtx
从parent
的children
集合 中移除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func removeChild (parent Context, child canceler) { if s, ok := parent.(stopCtx); ok { s.stop() return } p, ok := parentCancelCtx(parent) if !ok { return } p.mu.Lock() if p.children != nil { delete (p.children, child) } p.mu.Unlock() }
如果parent
不是cancelCtx
类型的,则直接返回(因为只有 cancelCtx
才有 children
集合)
加锁,将集合中key
为child
的删除,解锁
3.5 timerCtx 1 2 3 4 5 6 type timerCtx struct { cancelCtx timer *time.Timer deadline time.Time }
timerCtx
在cancelCtx
的基础上,又做了一层封装,新增了一个time.Timer
用于定时终止context
,另外新增一个deadline
用于记录过期时间
3.5.1 Deadline方法 1 2 3 func (c *timerCtx) Deadline() (deadline time.Time, ok bool ) { return c.deadline, true }
返回过期时间
3.5.2 String方法 1 2 3 4 5 func (c *timerCtx) String() string { return contextName(c.cancelCtx.Context) + ".WithDeadline(" + c.deadline.String() + " [" + time.Until(c.deadline).String() + "])" }
返回context
的名字加上过期时间
3.5.3 cancel方法 1 2 3 4 5 6 7 8 9 10 11 12 13 func (c *timerCtx) cancel(removeFromParent bool , err, cause error ) { c.cancelCtx.cancel(false , err, cause) if removeFromParent { removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock() }
继承cancelCtx
的cancel
方法,进行处理
判断是否需要从 parent
的 children
集合 中移除,若是则进行处理
加锁、停止time.Timer
、解锁返回
3.6 valueCtx 1 2 3 4 type valueCtx struct { Context key, val any }
valueCtx
同样继承了Context
,同时一个valueCtx
只能存储一对kv
3.6.1 String方法 1 2 3 4 5 func (c *valueCtx) String() string { return contextName(c.Context) + ".WithValue(type " + reflectlite.TypeOf(c.key).String() + ", val " + stringify(c.val) + ")" }
返回valueCtx
的名字以及存储的kv
值
3.6.2 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) }
如果参数的key
等于他所保存的key
则返回所保存的value
否则去parent
的Context
去找
进一步观察是如何在parent
中寻找的
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 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 withoutCancelCtx: if key == &cancelCtxKey { return nil } c = ctx.c case *timerCtx: if key == &cancelCtxKey { return &ctx.cancelCtx } c = ctx.Context case backgroundCtx, todoCtx: return nil default : return c.Value(key) } } }
启动一个 for 循环,一直往上去父上下文中寻找
其中不同的上下文类型会有不同的处理方式;
找到匹配的 key,则将该value 进行返回.
4 创建context 4.1 cancelCtx 有两种方法,一个不带cause
,一个带有cause
1 2 3 4 5 6 7 8 9 func WithCancel (parent Context) (ctx Context, cancel CancelFunc) { c := withCancel(parent) return c, func () { c.cancel(true , Canceled, nil ) } } func WithCancelCause (parent Context) (ctx Context, cancel CancelCauseFunc) { c := withCancel(parent) return c, func (cause error ) { c.cancel(true , Canceled, cause) } }
4.2 withoutCancelCtx 1 2 3 4 5 6 func WithoutCancel (parent Context) Context { if parent == nil { panic ("cannot create context from nil parent" ) } return withoutCancelCtx{parent} }
4.3 Deadline 同样有两种方法,一个不带cause
,一个带有cause
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 func WithDeadline (parent Context, d time.Time) (Context, CancelFunc) { return WithDeadlineCause(parent, d, nil ) } func WithDeadlineCause (parent Context, d time.Time, cause error ) (Context, CancelFunc) { if parent == nil { panic ("cannot create context from nil parent" ) } if cur, ok := parent.Deadline(); ok && cur.Before(d) { return WithCancel(parent) } c := &timerCtx{ deadline: d, } c.cancelCtx.propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true , DeadlineExceeded, cause) return c, func () { c.cancel(false , Canceled, nil ) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(dur, func () { c.cancel(true , DeadlineExceeded, cause) }) } return c, func () { c.cancel(true , Canceled, nil ) } }
4.4 Timeout 同样有两种方法,一个不带cause
,一个带有cause
,最后都是通过WithDeadlineCause
来创建的
1 2 3 4 5 6 7 func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) } func WithTimeoutCause (parent Context, timeout time.Duration, cause error ) (Context, CancelFunc) { return WithDeadlineCause(parent, time.Now().Add(timeout), cause) }
4.5 ValueCtx 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} }