Go context to understand graphic language programming language source code to achieve the core

Foundation to build the base

Thread programming language based on some of the design

ThreadGroup

image.png
ThreadGroup it is based on a commonly used concept of concurrent threads programming language, when a thread derive a sub-thread usually added to the parent thread's thread group (not specify a thread group), the last can be controlled set by ThreadGroup exit thread and other operations, and then go language goroutine no clear relationship between this parent / children, if you want to exit all goroutine on the current call chain will need to use context

ThreadLocal

In the thread-based programming language, language can usually be based on ThreadLocal to carry out some thread-local storage, is to key / value storage via a Map in nature and in go, there are no ThreadLocal design, passed in key / value when, in addition to the parameters to be passed through, it may be performed by the context transfer context information

context typical application scenarios

Scenes achieve principle
Contextual information transfer WithValue To save key pairs through key / value attribute an internal, can not be modified, can only be replaced by a cover worth of
Notice of withdrawal WithCancel To inform common exit channel by listening notice

Recursive context data acquisition

image.png
Because there is no use map in the context go inside data to be saved, so when the actual acquisition, is recursive up layer by layer from the beginning of the current layer, until it finds a matching key

In fact, we analogy ThreadGroup, because goroutine itself does not conceptually subordinate, but in fact we can achieve data transfer through parent-child relationship context, data can be set in a context goroutine, and then passed to the derived goroutine

Cancellation notice

image.png
既然通过context来构建parent/child的父子关系,在实现的过程中context会向parent来注册自身,当我们取消某个parent的goroutine, 实际上上会递归层层cancel掉自己的child context的done chan从而让整个调用链中所有监听cancel的goroutine退出

那如果一个child context的done chan为被初始化呢?那怎么通知关闭呢,那直接给你一个closedchan已经关闭的channel那是不是就可以了呢

带有超时context

image.png
如果要实现一个超时控制,通过上面的context的parent/child机制,其实我们只需要启动一个定时器,然后在超时的时候,直接将当前的context给cancel掉,就可以实现监听在当前和下层的额context.Done()的goroutine的退出

Background与TODO

image.png
Backgroud其实从字面意思就很容易理解,其实构建一个context对象作为root对象,其本质上是一个共享的全局变量,通常在一些系统处理中,我们都可以使用该对象作为root对象,并进行新context的构建来进行上下文数据的传递和统一的退出控制

那TODO呢?通常我们会给自己立很多的todo list,其实这里也一样,我们虽然构建了很多的todo list, 但大多数人其实啥也不会做,在很多的函数调用的过程中都会传递但是通常又不会使用,比如你既不会监听退出,也不会从里面获取数据,TODO跟Background一样,其背后也是返回一个全局变量

不可变性

通常我们使用context都是做位一个上下文的数据传递,比如一次http request请求的处理,但是如果当这次请求处理完成,其context就失去了意义,后续不应该继续重复使用一个context, 之前如果超时或者已经取消,则其状态不会发生改变

源码实现

context接口

type Context interface {
    // Deadline返回一个到期的timer定时器,以及当前是否以及到期
    Deadline() (deadline time.Time, ok bool)

    // Done在当前上下文完成后返回一个关闭的通道,代表当前context应该被取消,以便goroutine进行清理工作
    // WithCancel:负责在cancel被调用的时候关闭Done
    // WithDeadline: 负责在最后其期限过期时关闭Done
    // WithTimeout:负责超时后关闭done
    Done() <-chan struct{}

    // 如果Done通道没有被关闭则返回nil
    // 否则则会返回一个具体的错误
    // Canceled 被取消
    // DeadlineExceeded 过期
    Err() error
    // 返回对应key的value
    Value(key interface{}) interface{}
}

emptyCtx

emptyCtx是一个不会被取消、没有到期时间、没有值、不会返回错误的context实现,其主要作为context.Background()和context.TODO()返回这种root context或者不做任何操作的context

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

比较有意思的实现时emptyCtx的String方法,该方法可以返回当前context的具体类型,比如是Background还是TODO, 因为background和todo是两个全局变量,这里通过取其地址来进行对应类型的判断

cancelCtx

image.png

结构体

cancelCtx结构体内嵌了一个Context对象,即其parent context,同时内部还通过children来保存所有可以被取消的context的接口,后续当当前context被取消的时候,只需要调用所有canceler接口的context就可以实现当前调用链的取消

type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields 保护属性
    done     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
}

Done

Done操作返回当前的一个chan 用于通知goroutine退出


func (c *cancelCtx) Done() <-chan struct{} {
    c.mu.Lock()
    if c.done == nil {
        c.done = make(chan struct{})
    }
    d := c.done
    c.mu.Unlock()
    return d
}

cancel

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
    if err == nil {
        panic("context: internal error: missing cancel error")
    }
    // context一旦被某个操作操作触发取消后,就不会在进行任何状态的修改
    c.mu.Lock()
    if c.err != nil {
        c.mu.Unlock()
        return // already canceled
    }
    c.err = err
    if c.done == nil {
        c.done = closedchan
    } else {
        // close当前chan
        close(c.done)
    }
    // 调用所有children取消
    for child := range c.children {
        child.cancel(false, err)
    }
    c.children = nil
    c.mu.Unlock()

    // 是否需要从parent context中移除,如果是当前context的取消操作,则需要进行该操作
    // 否则,则上层context会主动进行child的移除工作
    if removeFromParent {
        removeChild(c.Context, c)
    }
}

timerCtx

image.png
timerCtx主要是用于实现WithDeadline和WithTimer两个context实现,其继承了cancelCtx接口,同时还包含一个timer.Timer定时器和一个deadline终止实现

2.4.1 结构体

timerCtx

type timerCtx struct {
    cancelCtx
    timer *time.Timer // timer定时器

    deadline time.Time //终止时间
}

取消方法

取消方法就很简单了首先进行cancelCtx的取消流程,然后进行自身的定时器的Stop操作,这样就可以实现取消了

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()
}

valueCtx

其内部通过一个key/value进行值得保存,如果当前context不包含着值就会层层向上递归

type valueCtx struct {
    Context
    key, val interface{}
}

func (c *valueCtx) String() string {
    return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
    return c.Context.Value(key)
}

propagateCancel

设计目标

propagateCancel主要设计目标就是当parent context取消的时候,进行child context的取消, 这就会有两种模式:
1.parent取消的时候通知child进行cancel取消
2.parent取消的时候调用child的层层递归取消

parentCancelCtx

context can be arbitrarily nested context tree structure composed of a N layer, the above two modes in combination, can be found when the parent is cancelCtx, timerCtx any one time, on the use of the second mode, the parent of the child to cancel call exit to complete the call chain, and vice versa using the first mode of listening Done

func parentCancelCtx(parent Context) (*cancelCtx, bool) {
    for {
        switch c := parent.(type) {
        case *cancelCtx:
            return c, true  // 找到最近支持cancel的parent,由parent进行取消操作的调用
        case *timerCtx:
            return &c.cancelCtx, true // 找到最近支持cancel的parent,由parent进行取消操作的调用
        case *valueCtx:
            parent = c.Context // 递归
        default:
            return nil, false
        }
    }
}

Core implementation

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
            // 如果发现parent已经取消就直接进行取消
            child.cancel(false, p.err)
        } else {
            if p.children == nil {
                p.children = make(map[canceler]struct{})
            }
            // 否则加入parent的children map中
            p.children[child] = struct{}{}
        }
        p.mu.Unlock()
    } else {
        go func() {
            select {
            case <-parent.Done():
                // 监听parent DOne完成, 此处也不会向parent进行注册
                child.cancel(false, parent.Err())
            case <-child.Done():
            }
        }()
    }
}

WithDeadline

With the above basic learning WithDeadline, it is a lot easier, WithDeadline will set a deadline, we need to wait for the current time can be calculated by how long it can be canceled

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
        // The current deadline is already sooner than the new one.
        return WithCancel(parent)
    }
    c := &timerCtx{
        cancelCtx: newCancelCtx(parent),
        deadline:  d,
    }
    // 监听parent的取消,或者向parent注册自身
    propagateCancel(parent, c)
    dur := time.Until(d)
    if dur <= 0 {
        // 已经过期
        c.cancel(true, DeadlineExceeded) // deadline has already passed
        return c, func() { c.cancel(false, Canceled) }
    }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
        c.timer = time.AfterFunc(dur, func() {
            // 构建一个timer定时器,到期后自动调用cancel取消
            c.cancel(true, DeadlineExceeded)
        })
    }
    // 返回取消函数
    return c, func() { c.cancel(true, Canceled) }
}

Backgroup 与 ALL

In many calls in the underlying middleware are passed through the context information, which is the most common and Backgroup Todo, though they are based emptyCtx achieved, but more inclined Backgroup as a parent context for subsequent call chain context of the whole use root, and usually indicates that a subsequent TODO not carry out any operation, simply because the use of parameters to be passed

Original link http://www.sreguide.com/go/context.html
Micro Signal: baxiaoshi2020
concern Bulletin No. Read more source code analysis of the article 21 days greenhouses
More Articles concern www.sreguide.com
article by a blog article multiple platforms OpenWrite release

Guess you like

Origin www.cnblogs.com/buyicoding/p/12155169.html