概述
context是golang中上下文中用来传值,超时控制的结构体。比如在很多在注明的开源框架中都实现了自己的context。
具体实现
先看一下context的具体实现,在源码中,context是一个非空接口,然后实现是
// A Context carries a deadline, a cancellation 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.
// The close of the Done channel may happen asynchronously,
// after the cancel function returns.
//
// WithCancel arranges for Done to be closed when cancel is called;
// WithDeadline arranges for Done to be closed when the deadline
// expires; WithTimeout arranges for Done to be closed when the timeout
// elapses.
//
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See https://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancellation.
Done() <-chan struct{}
// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
// After Err returns a non-nil error, successive calls to Err return the same error.
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.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.WithValue and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stored using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key any) any
}
从定义可以看出来,Context总有三个方法,分别是 Deadline(),Done(),Err(),Value(key any).
Deadline() (deadline time.Time, ok bool)
这个方法的返回值是deadline time.Time, ok bool.分别是context被取消的时间和deadline是否有被设置,如果是false那么说明Context没有被设置
Done() <-chan struct{}
注意这个方法的返回值是<-chan struct{},主要是为了告诉用户这个Context是否被取消了,一般都是和配合select case使用。然后通常是close后触发select case,然后引出来接下来的逻辑。
Err() error
返回值是error。当Done被触发的时候,Err返回的是为什么这个context会被关闭的错误。
Value(key any) any
这个是进行获取值。在使用context进行上下文传递的时候,往往需要携带值,就可以通过这个方法。需要注意的是为了保证不会被覆盖,官方的推荐是key需要是未导出的变量。看官方的示例应该很清楚了。
以上是context作为非空接口的需要实现的方法。然后看一下context的几个实现,分别是emptyCtx,cancelCtx,timerCtx,valueCtx。接下来具体介绍一个
emptyCtx
这个从名字也很容易看出,是空Ctx。定义也很简单是
// 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 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"
}
可以看出emptyCtx就是一个自定义类型。然后也实现了Context的方法。需要注意的是,虽然emptyCtx看起来什么都没有做,但是常用的context.Background和context,TODO最后返回的就是emptyCtx。如下
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
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
return todo
}
从官方的备注来看,Background 是返回的background,而TODO方法是todo。而这两个都是emptyCtx类型。
区别是Background是在最顶层的时候,也是方法的入口的时候创建,然后一层一层传递下去。
TODO方法是应该该穿context的地方却没有传,就是context为nil的时候,使用这个方法。
cancelCtx
定义
从名字上面可以看出,这个是做取消使用的。然后看一下定义
// 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 atomic.Value // of 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
}
看一个这个结构体,然后首先是Context的非空接口,其实因为context是一层一层记录的这个记录的这个cancelCtx的上一级Context。
mu 就是保证多线程操作的时候,保证线程安全。
done 。类似是atomic.Value,存的是这个chan struct{},并且在调用cancel的时候,通知其他线程,这个后面介绍方法的时候会说
children 。存的是后续自己的子集context,并且调用cancel的时候会一起通知。
err就是取消的原因。
初始化
看一下初始化的方法,初始化是WithCancel
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
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在传的时候,parent不能为nil否则会导致panic。
然后调用的newCancelCtx方法,其实就是创建一个cancelCtx的返回值
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
接下来调用的是propagateCancel方法。看一下这个方法官方的实现:
// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
// 判断父ctx是否有Done方法。没有则跳过
done := parent.Done()
if done == nil {
return // parent is never canceled
}
// 如果有 判断 是否已经关闭,如果已经关闭那么调用cancel 把创建的cancelCtx也取消了。
select {
case <-done:
// parent is already canceled
child.cancel(false, parent.Err())
return
default:
}
// 判断parent 是否是cancelCtx。
if p, ok := parentCancelCtx(parent); ok {
// 如果是给父ctx加锁
p.mu.Lock()
// 判断是否已经被关闭 如果被关闭那么把子节点关闭
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
// 把child加入到父ctx的children的map中
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 如果父ctx不是cancelCtx,那么新开一个goroutines去进行监听parent的状态,如果关闭了,那么把子ctx也关闭。
atomic.AddInt32(&goroutines, +1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
再看看parentCancelCtx 这个方法,这个方法在后面用的还是挺多的,其实就是判断传入的ctx是否是没有关闭的cancelCtx,看一下源码
// parentCancelCtx returns the underlying *cancelCtx for parent.
// It does this by looking up parent.Value(&cancelCtxKey) to find
// the innermost enclosing *cancelCtx and then checking whether
// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
// 判断父节点是否有done或者是否已经关闭
if done == closedchan || done == nil {
return nil, false
}
// 判断是否是cancelCtx
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
}
取消方法
这个方法是调用WithCancel后,返回的第二个参数,也就是cancel
这里看一下cancel这个方法,这个也是cancel的核心方法。还是一样说明也在方法的注释中
// 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) {
// 没有err直接pandic
if err == nil {
panic("context: internal error: missing cancel error")
}
// 先加锁
c.mu.Lock()
// err不为空说明已经关闭了
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
// 设置 err
c.err = err
// 获取要关闭的cha
d, _ := c.done.Load().(chan struct{})
// 没有那么设置closedchan 注意这个和parentCancelCtx刚开始的判断对应
if d == nil {
c.done.Store(closedchan)
} else {
// 如果已经存在了那么close
close(d)
}
// 取消掉当前所有子节点的ctx
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
// 设置子节点为nil释放内存
c.children = nil
c.mu.Unlock()
// 是否将自己从父节点删除掉
if removeFromParent {
removeChild(c.Context, c)
}
}
// 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()
}
这样cancel就走完了
方法
这里cancel重写了Value,Done,Err().这里说一下Done方法,
如下
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{})
}
其实就是生成了一个chan struct{}放到了c.done中,方便后面cancel的时候使用.
这里cancelCtx就说完了
timerCtx
这个ctx可以看出来是和时间相关的ctx,先看一下定义
// 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
}
从官方的注释可以看出,timerCtx是借助于cancelCtx。因为cancelCtx是需要用户手动进行取消,而timerCtx是在时间达到一定时间后,启动调用取消,差不多是这个意思。
然后timerCtx 比cancelCtx 就多了一个timer和deadline。
定义
timerCtx 的生成是通过WithDeadline方法,先看定义
// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
// 判断parent的Deadline,如果已经在传入的时间之前,那么退化成cancel。因此父ctx取消,会把子节点取消。
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
// 生成 timerCtx 先生成newCancelCtx,填充deadline
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 同cancel一样
propagateCancel(parent, c)
// 判断到未来的d需要多久时间
dur := time.Until(d)
// 如果少于0,那么所有取消所有子节点.并且返回。
if dur <= 0 {
// 这里是true
c.cancel(true, DeadlineExceeded) // deadline has already passed
// 返回的false
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
// 设置一个time.AfterFunc的方法,在dur后,会自动触发取消操作
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
然后看一下timeCtx的cancel方法,看看和cancelCtx有没有什么区别。
func (c *timerCtx) cancel(removeFromParent bool, err error) {
// 先调用cancelCtx的cancel方法,注意这里是false。
// 将子节点都去掉
c.cancelCtx.cancel(false, err)
// 如果true,需要将自己从父节点删除
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()
}
然后需要注意的是timerCtx只是重写了Deadline()方法,其他Done()等方法和cancelCtx是一样的,所以理解了cancelCtx,timerCtx也不难理解。
valueCtx
这个从名字可以看出来是存值,因为ctx是跟随方法的整个流程,因此需要上下文进行传递值.
先看看定义;
// 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 any
}
从这个valueCtx,只是新增了key,val 的结构体成员,类型是any也就是interface.
定义
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using context. Users of WithValue should define their own
// types for keys. To avoid allocating when assigning to an
// interface{}, context keys often have concrete type
// struct{}. Alternatively, exported context key variables' static
// type should be a pointer or interface.
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}
}
从官方的注释看出来,key首先需要是自定义的key,然后对于value来说,为了避免再次分配内存,value的值,最好是interface或者指针。
对于valueCtx 而言,只是重写了Value方法,看一下Value这个方法。
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)
}
}
}
判断当前ctxkey是否一致,不存在那么往上一级的ctx去找,然后调用value方法,然后一直循环,没有那么就是emptyCtx这个,然后返回nil.
这里说到这里ctx就说完了,核心不难看出,ctx主要是利用了go中channel和锁,实现了多线程之间的同步。