美文网首页
golang context

golang context

作者: GGGGGGGG_8721 | 来源:发表于2019-12-29 20:44 被阅读0次

    最初接触golang http源码的时候就发现有一个比较特别的package context,并且在后面学习etcd源码的时候发现几乎每一个回调相关的函数都会有一个ctx参数,顺便了解下它的实现原理以及设计思想。

    context 的作用

    context结合自己工作实践,我认为context主要有以下两个作用:
    1.传递数据
    2.递归的取消子任务

      context包在golang源码的golang.org/x/net/context目录下,context主要用在结束若干个相互具有父子(主任务子任务)关系的goroutine,context内部本身就是以一种树形递归的形式去组织各个context节点,父节点的取消,会递归的把取消信号发送给它的子节点。

    Context接口的定义如下:

    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.
        //
        // 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() <-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{}
    }
    

    Deadline:主要用于设定超时时间的Context上,它的返回值用于表示该Context取消的时间点。
    Done:函数主要是返回一个单向的接收channel,在Context被取消之后该接收channel会返回一个值或者被关闭,都会执行select中对应的语句,这些语句一般是结束当前函数执行,并做一些清理工作。
    Err:返回该Conext被取消的原因。
    Value:返回该Context中key关联的值。

      由于Context是基于父子类关系的属性结构组织的,无论我们是自己定义的Context还是使用golib提供的Context类,我们都会基于一个父Context,实际开发中我们使用的这个父Context大多数情况下是Background函数返回的Context:

    // 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
    }
    

      Background函数返回的Context是一个空的context,没有deadline,不可以取消,并且没有值,它的定义如下:

    // 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"
    }
    
    var (
        background = new(emptyCtx)
        todo       = new(emptyCtx)
    )
    

      除了Background返回的一个类似与超级父类的一个Context,帮助我们自定义以及创建自己的Context,但是golang提供的cancelCtx以及timerCtx已经能够满足我们大多数需求了,golang提供了响应的函数专门用来创建这两个Context。
      cancelCtx比较核心的是cancel函数,它在取消当前Context的同时,也递归的取消以它作为祖先节点的所有Context节点。另外一个函数是propagateCancel,它主要是防止用来创建当前cancalCtx节点的parent没有递归的调用子节点的cancel,因为cancel并不是Context实现的标准方法,parent很可能就不知道子节点怎样取消,cancalCtx中cancel也是建立在canceler的接口之上实现的。cancelCtx的实现:

    // A canceler is a context type that can be canceled directly. The
    // implementations are *cancelCtx and *timerCtx.
    type canceler interface {
        cancel(removeFromParent bool, err error)
        Done() <-chan struct{}
    }
    
    // A cancelCtx can be canceled. When canceled, it also cancels any children
    // that implement canceler.
    type cancelCtx struct {
        Context
    
        done chan struct{} // closed by the first cancel call.
    
        mu       sync.Mutex
        children map[canceler]bool // set to nil by the first cancel call
        err      error             // set to non-nil by the first cancel call
    }
    
    // newCancelCtx returns an initialized cancelCtx.
    func newCancelCtx(parent Context) *cancelCtx {
        return &cancelCtx{
            Context: parent,
            done:    make(chan struct{}),
        }
    }
    
    // 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]bool)
                }
                p.children[child] = true
            }
            p.mu.Unlock()
        } else {
            go func() {
                select {
                // 保证父节点被取消的时候子节点会被取消
                case <-parent.Done():
                    child.cancel(false, parent.Err())
                case <-child.Done():
                }
            }()
        }   
    }
    
    func (c *cancelCtx) Done() <-chan struct{} {
        return c.done
    }
    
    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
        // 通过关闭done channel来达到通知select的
        close(c.done)
        // 循环遍历children节点,递归的取消children Context
        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的创建:

    // 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) {
        c := newCancelCtx(parent)
        propagateCancel(parent, c)
        return c, func() { c.cancel(true, Canceled) }
    }
    
    

      timerCtx继承了canceler接口,并重写了cancel方法,并增加了一个定时器timer以及一个绝对时间确定的deadline,通过把对当前timerCtx的取消操作添加到系统定时器内,并在cancel调用cancelCtx.cancel来达到递归取消子Context节点的目的,timerCtx的实现:

    // 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, c.deadline.Sub(time.Now()))
    }
    
    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()
    }
    
    

    timerCtx的创建:

    // 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, deadline time.Time) (Context, CancelFunc) {
        if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
            // The current deadline is already sooner than the new one.
            return WithCancel(parent)
        }
        c := &timerCtx{
            cancelCtx: newCancelCtx(parent),
            deadline:  deadline,
        }
        propagateCancel(parent, c)
        d := deadline.Sub(time.Now())
        if d <= 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(d, func() {
                c.cancel(true, DeadlineExceeded)
            })
        }
        return c, func() { c.cancel(true, Canceled) }
    }
    
    // WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
    //
    // Canceling this context releases resources associated with it, so code should
    // call cancel as soon as the operations running in this Context complete:
    //
    //  func slowOperationWithTimeout(ctx context.Context) (Result, error) {
    //      ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
    //      defer cancel()  // releases resources if slowOperation completes before timeout elapses
    //      return slowOperation(ctx)
    //  }
    func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
        return WithDeadline(parent, time.Now().Add(timeout))
    }
    

      另外,WithCancel、WithDeadline以及WithTimeout都返回了一个Context以及一个CancelFunc函数,返回的Context也就是我们当前基于parent创建了cancelCtx或则timerCtx,通过CancelFunc我们可以取消当前Context,即使timerCtx还未超时。

    相关文章

      网友评论

          本文标题:golang context

          本文链接:https://www.haomeiwen.com/subject/exduoctx.html