Golang context
本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分。
ps: 作者本着开源分享的精神撰写本篇文章,如果出现任何误差务必留言指正,作者会在第一时间内修正,共同维护一个好的开源生态,谢谢!!!
一、简介
作者所讲的context的包名称是: "golang.org/x/net/context" ,希望读者不要引用错误了。
在godoc中对context的介绍如下:

1 Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes. 2 3 As of Go 1.7 this package is available in the standard library under the name context. https://golang.org/pkg/context. 4 5Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between must propagate the Context, optionally replacing it with a modified copy created using WithDeadline, WithTimeout, WithCancel, or WithValue. 6 7Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation: 8 9Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx: 10 11func DoSomething(ctx context.Context, arg Arg) error { 12 // ... use ctx ... 13} 14 15Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use. 16 17Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. 18 19The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines. 20 21See http://blog.golang.org/context for example code for a server that uses Contexts.
View Code
鉴于作者英文水平有限,在这里不进行对照翻译,以免误导读者。它的第一句已经介绍了它的作用了:一个贯穿API的边界和进程之间的context 类型,可以携带deadlines、cancel signals和其他信息。就如同它的中文翻译一样:上下文。在一个应用服务中会并行运行很多的goroutines或进程, 它们彼此之间或者是从属关系、竞争关系、互斥关系,不同的goroutines和进程进行交互的时候需要进行状态的切换和数据的同步,而这就是context包要支持的功能。
二、解析
context的接口定义如下:
每一个接口都有详细的注释,这里就不重复了。 在context的源码中有以下几个结构体实现了Context Interface:
1// A Context carries a deadline, a cancelation signal, and other values across 2// API boundaries.Context's methods may be called by multiple goroutines simultaneously. 3type Context interface { 4 // Deadline returns the time when work done on behalf of this context 5 // should be canceled. Deadline returns ok==false when no deadline is 6 // set. Successive calls to Deadline return the same results. 7 Deadline() (deadline time.Time, ok bool) 8 // Done returns a channel that's closed when work done on behalf of this 9 // context should be canceled. Done may return nil if this context can 10 // never be canceled. Successive calls to Done return the same value. 11 Done() <-chan struct{} 12 // Err returns a non-nil error value after Done is closed. Err returns 13 // Canceled if the context was canceled or DeadlineExceeded if the 14 // context's deadline passed. No other values for Err are defined. 15 // After Done is closed, successive calls to Err return the same value. 16 Err() error 17 // Value returns the value associated with this context for key, or nil 18 // if no value is associated with key. Successive calls to Value with 19 // the same key returns the same result. 20 Value(key interface{}) interface{} 21}
2.1 empty context
1// An emptyCtx is never canceled, has no values, and has no deadline. It is not 2// struct{}, since vars of this type must have distinct addresses. 3type emptyCtx int 4 5func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { 6 return 7} 8 9func (*emptyCtx) Done() <-chan struct{} { 10 return nil 11} 12 13func (*emptyCtx) Err() error { 14 return nil 15} 16 17func (*emptyCtx) Value(key interface{}) interface{} { 18 return nil 19} 20 21func (e *emptyCtx) String() string { 22 switch e { 23 case background: 24 return "context.Background" 25 case todo: 26 return "context.TODO" 27 } 28 return "unknown empty Context" 29}
这是一个空的ctx类型,每一个返回值都为空,它什么都功能都不具备,主要的作用是作为所有的context类型的起始点,**context.Background()**函数返回的就是这中类型的Context:
1var ( 2 background = new(emptyCtx) 3 todo = new(emptyCtx) 4) 5 6// Background returns a non-nil, empty Context. It is never canceled, has no 7// values, and has no deadline. It is typically used by the main function, 8// initialization, and tests, and as the top-level Context for incoming 9// requests. 10func Background() Context { 11 return background 12}
empty context的作用作者下面会阐述。
2.2 cancle context
1// A cancelCtx can be canceled. When canceled, it also cancels any children 2// that implement canceler. 3type cancelCtx struct { 4 Context 5 6 mu sync.Mutex // protects following fields 7 done chan struct{} // created lazily, closed by first cancel call 8 children map[canceler]struct{} // set to nil by the first cancel call 9 err error // set to non-nil by the first cancel call 10} 11 12func (c *cancelCtx) Done() <-chan struct{} { 13 c.mu.Lock() 14 if c.done == nil { 15 c.done = make(chan struct{}) 16 } 17 d := c.done 18 c.mu.Unlock() 19 return d 20} 21 22func (c *cancelCtx) Err() error { 23 c.mu.Lock() 24 defer c.mu.Unlock() 25 return c.err 26} 27 28func (c *cancelCtx) String() string { 29 return fmt.Sprintf("%v.WithCancel", c.Context) 30} 31 32// cancel closes c.done, cancels each of c's children, and, if 33// removeFromParent is true, removes c from its parent's children. 34func (c *cancelCtx) cancel(removeFromParent bool, err error) { 35 if err == nil { 36 panic("context: internal error: missing cancel error") 37 } 38 c.mu.Lock() 39 if c.err != nil { 40 c.mu.Unlock() 41 return // already canceled 42 } 43 c.err = err 44 if c.done == nil { 45 c.done = closedchan 46 } else { 47 close(c.done) 48 } 49 for child := range c.children { 50 // NOTE: acquiring the child's lock while holding parent's lock. 51 child.cancel(false, err) 52 } 53 c.children = nil 54 c.mu.Unlock() 55 56 if removeFromParent { 57 removeChild(c.Context, c) 58 } 59}
cancelctx中的成员变量:
`done chan struct{}`: 在调用Done()函数时会将该变量返回,这可以用在多goroutines之间进行状态同步;
`children map[canceler]struct{}`: 一个context可以被其他context 引用,被引用的context为parant,引用context为children,该变量包含所有的children context;
`Context`: 关联了Context接口,实现了Done()和Err(),但是没有实现Value()和Deadline();
cancel函数是一个受保护的函数,不能在外部进行调用。可以看到在执行这个函数的时候 done channel会被关闭掉,同时它会调用所有的children context的cancel函数,但是没有解除彼此的依赖关系。这实际上比较好理解,因为children context的生命周期是依赖与parant context的。同时它还要判断是否调用 removeChild(c.Context, c)函数将解除对parant context的引用关系。
在context.WithCancel(parent Context) 函数中返回的就是cancelCtx;
2.3 timer context
1// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to 2// implement Done and Err. It implements cancel by stopping its timer then 3// delegating to cancelCtx.cancel. 4type timerCtx struct { 5 cancelCtx 6 timer *time.Timer // Under cancelCtx.mu. 7 deadline time.Time 8} 9 10func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { 11 return c.deadline, true 12} 13 14func (c *timerCtx) String() string { 15 return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline)) 16} 17 18func (c *timerCtx) cancel(removeFromParent bool, err error) { 19 c.cancelCtx.cancel(false, err) 20 if removeFromParent { 21 // Remove this timerCtx from its parent cancelCtx's children. 22 removeChild(c.cancelCtx.Context, c) 23 } 24 c.mu.Lock() 25 if c.timer != nil { 26 c.timer.Stop() 27 c.timer = nil 28 } 29 c.mu.Unlock() 30}
timer context 中的成员变量:
`cancelCtx`: timerCtx 关联了 canelCtx类型;
`timer`: 一个定时器,用来设置超时的时间;
`dealine`: 一个时间类型,用来记录死亡时间;
cancel函数 :1)它会先触发c.cancelCtx的cancel操作,但没有解除c.cancelCtx与parentCtx的依赖关系。2)判断是否去解除自身对于parentCtx的依赖, 3)停止它的timer,这个时候计时就结束了;
它只实现了Deadline()函数,值得注意的是timeCtx和其包含的cancleCtx 是依赖于同一个parentCtx的。
在WithDeadline(parent Context, deadline time.Time)和WithTimeout(parent Context, timeout time.Duration)函数中返回的就是 timerCtx;
2.4 value context
1// A valueCtx carries a key-value pair. It implements Value for that key and 2// delegates all other calls to the embedded Context. 3type valueCtx struct { 4 Context 5 key, val interface{} 6} 7 8func (c *valueCtx) String() string { 9 return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) 10} 11 12func (c *valueCtx) Value(key interface{}) interface{} { 13 if c.key == key { 14 return c.val 15 } 16 return c.Context.Value(key) 17}
value context成员变量:
Context:它关联了Context接口
key,val interface{} :两个变量形成一个key-value结构;
它只实现了Value()函数,可以返回key对应的value,这里需要注意的是**,在查询value的时候,如果当前context中没有,会向上级的context进行搜索,递归查询**。
WithValue(parent Context, key, val interface{}) 函数返回的即为value context。
2.5 总结
综上四种类型的context 总结如下:
Name
Deadline
Done
Err
Value
继承
empty
nil
cancel
-
-
Context
timer
-
-
-
canelCtx
value
-
-
-
Context
我们可以发现除了empty以外其他三种类型都没有完全去实现Context接口定义的所有函数,如果直接实例化一个cancelCtx对象,但是没有对 Context部分进行赋值,当调用其Value和Deadline函数会崩溃,timerCtx和valueCtx也是同样的道理。请读者一定要记住以上四种类型,这样你会很容易理解下面的内容。
三、 Context的使用
3.1 Context 常用函数
我们在上面的介绍过程中提到了很多函数:
1//创建一个Cancel contextfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc) 2//创建一个带有 deadline的Timer context 3func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 4//创建一个带有超时的Timer context 5func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 6//创建一个Value context 7func WithValue(parent Context, key, val interface{}) Context
这些函数都是在使用Context中经常用到的,我们接下来说明它们的功能。
WithCancel
介绍:
复制parentCtx,同时创建一个新的Done Channel返回Cancel函数,当以下两种情况发生时会关闭Done Channel,触发Done信号:
1) 返回的Cancel函数被调用;
2) parentCtx触发了Done信号;
通常一个Context生命周期截止了(不再被需要的时候)就要立刻调用Cancel函数
1func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 2 c := newCancelCtx(parent) 3 propagateCancel(parent, &c) 4 return &c, func() { c.cancel(true, Canceled) } 5}
在newcancel函数中实例化一个cancel context对象:
1// newCancelCtx returns an initialized cancelCtx. 2func newCancelCtx(parent Context) cancelCtx { 3 return cancelCtx{Context: parent} 4}
propagateCancel如注释所述:向上找到最近的可以被依赖的父context,将子context放入 parent的children队列;如果找不到就开一个goroutines来等待主链退出的通知。
1// propagateCancel arranges for child to be canceled when parent is. 2func propagateCancel(parent Context, child canceler) { 3 if parent.Done() == nil { 4 return // parent is never canceled 5 } 6 if p, ok := parentCancelCtx(parent); ok { 7 p.mu.Lock() 8 if p.err != nil { 9 // parent has already been canceled 10 child.cancel(false, p.err) 11 } else { 12 if p.children == nil { 13 p.children = make(map[canceler]struct{}) 14 } 15 p.children[child] = struct{}{} 16 } 17 p.mu.Unlock() 18 } else { 19 go func() { 20 select { 21 case <-parent.Done(): 22 child.cancel(false, parent.Err()) 23 case <-child.Done(): 24 } 25 }() 26 } 27}
parentCancelCtx : 寻找沿着parant引用向上追溯,直到发现一个cancelCtx;
1// parentCancelCtx follows a chain of parent references until it finds a 2// *cancelCtx. This function understands how each of the concrete types in this 3// package represents its parent. 4func parentCancelCtx(parent Context) (*cancelCtx, bool) { 5 for { 6 switch c := parent.(type) { 7 case *cancelCtx: 8 return c, true 9 case *timerCtx: 10 return &c.cancelCtx, true 11 case *valueCtx: 12 parent = c.Context 13 default: 14 return nil, false 15 } 16 } 17}
它还返回一个函数指针,这个函数指针实际上就是执行cancelCtx中的cancel函数--c.cancel(true, Canceled),该操作会解除和parent ctx的依赖。
总的来说创建一个新的context就是在parant context中挂载一个 children context,也许传入的parent与新生成的ctx会挂载到同一个ctx下,也许会加入到parent contxt的children 队列中。我们要与上面的四种类型的context比较,empty context和 value context是不具备挂载children的能力的,而cancel context 和timer context 两种类型具备挂载chidren 的能力(实际上timerCtx的挂载能力是继承于canelCtx)。
但问题来了,在创建cancel context时候需要传入一个parent 参数,那么这个parent从哪里来?这时候就需要 func Background() Context 这个函数,它返回一个作为起始点的context对象,而这个BackgroundCtx是一个empty context,这就是empty context的作用。在回想一下上面的介绍是不是很合理的构造?
WithDeadline
复制parentCtx,在创建的时候如果parentCtx已经触发Deadline,则直接返回一个cancelCtx和Cancel函数,否则会将返回一个timeCtx和Cancel函数。在发生以下情况的时候Done信号会被触发:
1)Cancel函数被调用
2)当前时间超过设置的Deadline
3)parentCtx触发了Done信号
1func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { 2 if cur, ok := parent.Deadline(); ok && cur.Before(d) { 3 // The current deadline is already sooner than the new one. 4 return WithCancel(parent) 5 } 6 c := &timerCtx{ 7 cancelCtx: newCancelCtx(parent), 8 deadline: d, 9 } 10 propagateCancel(parent, c) 11 dur := time.Until(d) 12 if dur <= 0 { 13 c.cancel(true, DeadlineExceeded) // deadline has already passed 14 return c, func() { c.cancel(true, Canceled) } 15 } 16 c.mu.Lock() 17 defer c.mu.Unlock() 18 if c.err == nil { 19 c.timer = time.AfterFunc(dur, func() { 20 c.cancel(true, DeadlineExceeded) 21 }) 22 } 23 return c, func() { c.cancel(true, Canceled) } 24}
WithValue
复制parentCtx,保存传入的key-value键值对,没有Cancel函数被返回,但不代表该context没有done信号,只有在parentCtx的Done信号被触发的时候,才会发送。
1func WithValue(parent Context, key, val interface{}) Context { 2 if key == nil { 3 panic("nil key") 4 } 5 if !reflect.TypeOf(key).Comparable() { 6 panic("key is not comparable") 7 } 8 return &valueCtx{parent, key, val} 9}