【go语言之context】

概述

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和锁,实现了多线程之间的同步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值