32

golang context

 4 years ago
source link: https://studygolang.com/articles/25690
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最初接触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还未超时。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK