golang context用法详解

最初接触golang http源码的时候就发现有一个比较特别的package context,当时顺便把它的源码给看了下,了解下它的工作原理,并且在后面学习etcd源码的时候发现几乎每一个回调相关的函数都会有一个ctx参数。今天终于闲下来了,对于context这么常用的package还是做下笔记为好。

  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)