go context专题(二)- context设计目的和基本数据结构

go context专题(二)- context设计目的和基本数据结构


设计目的

context库的设计目的就是跟踪 goroutine调用树,并在在这些gouroutine调用树中传递通知和元数据。两个目的:

1.退出通知机制 (主要目的)

2.传递元数据 (辅助功能)


基本的数据结构

为了理解context的实现,需要理解下设计规则:context的创建者称为root节点,其一般是一个处理上下文的独立goroutine。root节点负责创建Context的具体对象,并将其传递到其下游调用的goroutine. 下游的goroutine可以继续封装改Context对象(使用装饰者模式)再传递更下游的goroutine.这些下游goroutine的Context 对象实例都要逐层向上注册。这样通过root节点的Context对象就可以遍历整个Context对象树,所以通知也能通知到下游的goroutine.

1.  2个接口

Context接口

是一个基本的接口,所有的Context对象都要实现该接口,context的使用者在在调用接口中都是使用Context最为参数类型。
具体解释如下
#Context接口
type Context interface {
		//如果Context实现了超时控制,该方法返回ok true deadline为超时时间,否则ok 为false
    Deadline() (deadline time.Time, ok bool)
    
    //后端被调的goroutine应该监听该方法返回的chan,以便及时的释放资源
    Done() <-chan struct{}
    
    //Done返回的chan收到通知的时候,才可以访问Err()获知因为什么原因被取消
    Err() error
    
    //可以访问上游goroutine传递给下游goroutine的值
    Value(key interface{}) interface{}
}

canceler接口

是一个扩展接口,其规定了取消通知的Context 具体类型需要实现的接口。context包中:*cancelCtx and *timerCtx 都实现了该接口。

 // A canceler is a context type that can be canceled directly. The                                    
 // implementations are *cancelCtx and *timerCtx.
 //一个Context 对象如果实现了canceler接口,可以被取消                                                      
 type canceler interface {                       
 //cancel 是主调goroutine负责调用
     cancel(removeFromParent bool, err error) 
 //Done方法返回的chan需要后端goroutine来监听,并及时退出                                                         
     Done() <-chan struct{}                                                                            
 }


2.  empty  Context结构

emptyCtx 实现了Context接口,但是不具备任何功能,因为其所有的方法都是空实现。其存在的目的是为Context的对象提供一个root根。
因为context的使用思路就是不停的调用context包提供的包装函数创建具有特殊功能的Context实例,使用emptyCtx的实例最为第一层包装。
#emptyCtx实现了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"                                                                    
} 

package 定义了两个全局变量和封装函数,返回两个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). TODO is recognized by static analysis tools that determine
// whether Contexts are propagated correctly in a program.
func TODO() Context {
    return todo
}


3.Context的具体实现类型

cancelCtx

cancelCtx 是一个实现了Context接口的具体类型,其同时实现Context和conceler接口,实现退出通知。注意其退出通知机制不但通知自己,同时也通知其children节点。
// 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                                 
}                                                                                                         
                                                                                                          
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
        //显示通知自己
    close(c.done)
	//循环调用children的cancel函数,由于parent已经cancel,所以此时child调用cancel传入的是false
     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)
    }
}



timerCtx

timerCtx 是一个实现了Context接口的具体类型,其内部封装了cancelCtx类型实例,同时也有个deadline变量,用来实现定时退出通知。
// 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()
}



valueCtx

valueCtx是一个实现了Context接口的具体类型,其内部封装了Context接口类型,同时也封装了一个k/v的存储变量,其是一个实现了数据传递
Context具体实现类型。
// 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 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)                                                                           
} 


4.包装函数

#下面这两个函数是在构成实例的初期构造取消树的根节点时使用Context的对象,为后续的With包装函数的调用创建一个输入参数parent.
func Background() Context
func TODO() Context

创建一个带有退出通知的Context具体对象,其实是创建一个cancelCtx的类型实例
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

创建一个带有超时通知的Context具体对象,其实是创建一个timerCtx的类型实例,其是对WithDeadline的简单封装
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

创建一个带有超时通知的Context具体对象,其实是创建一个timerCtx的类型实例
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

创建一个带有数据的Context具体对象,其实是创建一个valueCtx的类型实例
func WithValue(parent Context, key, val interface{}) Context


这些函数都有个共同的特点有个parent参数,其实这个就是实现Context通知树的必备条件。在goroutine的调用链 Context的实例被逐层的传递,没个层次又可以在传进来的Context实例上再封装自己的需求,但是这整个调用树需要有个结构来维护。其实这个维护过程就是在这些包装函数内部完成。


5.辅助函数

4中的上述的With开头的构造函数是给外部程序使用的开放函数,我们一直讨论的Context具体对象的链条关系的维护,实际是在With函数的内部进行维护的。现在来分析一下With函数内部说使用的通用函数。

func propagateCancel(parent Context, child canceler)其有如下几个功能:

1.判断parent的method  Done返回是否是nil,如果是,说明parent不是一个可取消的Context对象,也就无所谓取消构造树,那说明child就是取消构造树的根

2.判断parent的method  Done返回不是nil,向上回溯自己的祖先是否是cancelCtx类型实例,如果是的话就将chiild的子节点注册维护到那课关系树里面。

3.如果向上回溯自己的祖先不是cancelCtx类型实例,那说明整个链条的取消树不是连续的,有几颗取消树,所以,此时只需监听parent和自己的取消信号即可。

// 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 parentCancelCtx(parent Context) (*cancelCtx, bool)

判断parent 中是否封装有*cancelCtx的字段或者接口里面存放的底层类型是*cancelCtx类型。

// parentCancelCtx follows a chain of parent references until it finds a
// *cancelCtx. This function understands how each of the concrete types in this
// package represents its parent.
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
    for {
        switch c := parent.(type) {
        case *cancelCtx:
            return c, true
        case *timerCtx:
            return &c.cancelCtx, true
        case *valueCtx:
            parent = c.Context
        default:
            return nil, false
        }
    }
}

func removeChild(parent Context, child canceler)

如果parent封装有*cancelCtx的字段或者接口里面存放的底层类型是*cancelCtx类型,这将其构造树上的child节点删除

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









发布了200 篇原创文章 · 获赞 109 · 访问量 160万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览