本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者能够直接跳过去阅读使用部分。golang
ps: 做者本着开源分享的精神撰写本篇文章,若是出现任何偏差务必留言指正,做者会在第一时间内修正,共同维护一个好的开源生态,谢谢!!!ide
做者所讲的context的包名称是: "golang.org/x/net/context" ,但愿读者不要引用错误了。函数
在godoc中对context的介绍以下:ui
Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.
As of Go 1.7 this package is available in the standard library under the name context. https://golang.org/pkg/context.
Incoming 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.
Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:
Do 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:
func DoSomething(ctx context.Context, arg Arg) error {
// ... use ctx ...
}
Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.
Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.
See http://blog.golang.org/context for example code for a server that uses Contexts.
鉴于做者英文水平有限,在这里不进行对照翻译,以避免误导读者。它的第一句已经介绍了它的做用了:一个贯穿API的边界和进程之间的context 类型,能够携带deadlines、cancel signals和其余信息。就如同它的中文翻译同样:上下文。在一个应用服务中会并行运行不少的goroutines或进程, 它们彼此之间或者是从属关系、竞争关系、互斥关系,不一样的goroutines和进程进行交互的时候须要进行状态的切换和数据的同步,而这就是context包要支持的功能。
this
context的接口定义以下: spa
每个接口都有详细的注释,这里就不重复了。 在context的源码中有如下几个结构体实现了Context Interface:翻译
// A Context carries a deadline, a cancelation signal, and other values across // API boundaries.Context's methods may be called by multiple goroutines simultaneously. type Context interface { // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. Deadline() (deadline time.Time, ok bool) // Done returns a channel that's closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled. Successive calls to Done return the same value. Done() <-chan struct{} // Err returns a non-nil error value after Done is closed. Err returns // Canceled if the context was canceled or DeadlineExceeded if the // context's deadline passed. No other values for Err are defined. // After Done is closed, successive calls to Err return the same value. Err() error // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. Value(key interface{}) interface{} }
// 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" }
这是一个空的ctx类型,每个返回值都为空,它什么都功能都不具有,主要的做用是做为全部的context类型的起始点,context.Background()函数返回的就是这中类型的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 }
empty context的做用做者下面会阐述。 code
// 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) 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() defer c.mu.Unlock() return c.err } func (c *cancelCtx) String() string { return fmt.Sprintf("%v.WithCancel", c.Context) } // 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) } }
cancelctx中的成员变量:server
`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;
// 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 fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline)) } 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() }
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;
// 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{} } func (c *valueCtx) String() string { return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) } func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) }
value context成员变量:
Context:它关联了Context接口
key,val interface{} :两个变量造成一个key-value结构;
它只实现了Value()函数,能够返回key对应的value,这里须要注意的是,在查询value的时候,若是当前context中没有,会向上级的context进行搜索,递归查询。
WithValue(parent Context, key, val interface{}) 函数返回的即为value context。
综上四种类型的context 总结以下:
Name | Deadline | Done | Err | Value | 继承 |
empty | + | + | + | + | nil |
cancel | - | + | + | - | Context |
timer | + | - | - | - | canelCtx |
value | - | - | - | + | Context |
咱们能够发现除了empty之外其余三种类型都没有彻底去实现Context接口定义的全部函数,若是直接实例化一个cancelCtx对象,可是没有对 Context部分进行赋值,当调用其Value和Deadline函数会崩溃,timerCtx和valueCtx也是一样的道理。请读者必定要记住以上四种类型,这样你会很容易理解下面的内容。
咱们在上面的介绍过程当中提到了不少函数:
//建立一个Cancel context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
//建立一个带有 deadline的Timer context func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
//建立一个带有超时的Timer context func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
//建立一个Value context func WithValue(parent Context, key, val interface{}) Context
这些函数都是在使用Context中常常用到的,咱们接下来讲明它们的功能。
介绍:
复制parentCtx,同时建立一个新的Done Channel返回Cancel函数,当如下两种状况发生时会关闭Done Channel,触发Done信号:
1) 返回的Cancel函数被调用;
2) parentCtx触发了Done信号;
一般一个Context生命周期截止了(再也不被须要的时候)就要马上调用Cancel函数
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) } }
在newcancel函数中实例化一个cancel context对象:
// newCancelCtx returns an initialized cancelCtx. func newCancelCtx(parent Context) cancelCtx { return cancelCtx{Context: parent} }
propagateCancel如注释所述:向上找到最近的能够被依赖的父context,将子context放入 parent的children队列;若是找不到就开一个goroutines来等待主链退出的通知。
// propagateCancel arranges for child to be canceled when parent is. func propagateCancel(parent Context, child canceler) { if parent.Done() == nil { return // parent is never canceled } 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 { go func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() } }
parentCancelCtx : 寻找沿着parant引用向上追溯,直到发现一个cancelCtx;
// parentCancelCtx follows a chain of parent references until it finds a // *cancelCtx. This function understands how each of the concrete types in this // package represents its parent. func parentCancelCtx(parent Context) (*cancelCtx, bool) { for { switch c := parent.(type) { case *cancelCtx: return c, true case *timerCtx: return &c.cancelCtx, true case *valueCtx: parent = c.Context default: return nil, false } } }
它还返回一个函数指针,这个函数指针实际上就是执行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的做用。在回想一下上面的介绍是否是很合理的构造?
复制parentCtx,在建立的时候若是parentCtx已经触发Deadline,则直接返回一个cancelCtx和Cancel函数,不然会将返回一个timeCtx和Cancel函数。在发生如下状况的时候Done信号会被触发:
1)Cancel函数被调用
2)当前时间超过设置的Deadline
3)parentCtx触发了Done信号
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { 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(true, 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) } }
复制parentCtx,保存传入的key-value键值对,没有Cancel函数被返回,但不表明该context没有done信号,只有在parentCtx的Done信号被触发的时候,才会发送。
func WithValue(parent Context, key, val interface{}) Context { if key == nil { panic("nil key") } if !reflect.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val} }
[1] https://godoc.org/golang.org/x/net/context