您的位置:首页 > 理论基础 > 数据结构算法

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

2017-08-29 20:56 330 查看
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()
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: