go context专题(二)- context设计目的和基本数据结构
2017-08-29 20:56
330 查看
go context专题(二)- context设计目的和基本数据结构
1.退出通知机制 (主要目的)
2.传递元数据
(辅助功能)
对象实例都要逐层向上注册。这样通过root节点的Context对象就可以遍历整个Context对象树,所以通知也能通知到下游的goroutine.
具体解释如下
因为context的使用思路就是不停的调用context包提供的包装函数创建具有特殊功能的Context实例,使用emptyCtx的实例最为第一层包装。
package 定义了两个全局变量和封装函数,返回两个emptyCtx实例对象,通常用它们在第一次调用包装函数时传递进去。
Context具体实现类型。
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实例上再封装自己的需求,但是这整个调用树需要有个结构来维护。其实这个维护过程就是在这些包装函数内部完成。
func propagateCancel(parent Context, child canceler)其有如下几个功能:
1.判断parent的method Done返回是否是nil,如果是,说明parent不是一个可取消的Context对象,也就无所谓取消构造树,那说明child就是取消构造树的根
2.判断parent的method Done返回不是nil,向上回溯自己的祖先是否是cancelCtx类型实例,如果是的话就将chiild的子节点注册维护到那课关系树里面。
3.如果向上回溯自己的祖先不是cancelCtx类型实例,那说明整个链条的取消树不是连续的,有几颗取消树,所以,此时只需监听parent和自己的取消信号即可。
func parentCancelCtx(parent Context) (*cancelCtx, bool)
判断parent 中是否封装有*cancelCtx的字段或者接口里面存放的底层类型是*cancelCtx类型。
func removeChild(parent Context, child canceler)
如果parent封装有*cancelCtx的字段或者接口里面存放的底层类型是*cancelCtx类型,这将其构造树上的child节点删除
设计目的
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() }
相关文章推荐
- 数据结构基础(8) --单链表的设计与实现(1)之基本操作
- 数据结构基础(8) --单链表的设计与实现(1)之基本操作
- 【数据结构作业】实验三 单链表的基本操作(必做,设计性实验,4学时)
- 数据结构基础(8) --单链表的设计与实现(1)之基本操作
- 数据结构和算法设计专题之---二分查找(Java版)
- 面向数据结构设计的基本思想
- 数据结构基础(8) --单链表的设计与实现(1)之基本操作
- Go语言学习札记——第三章 Go基本的程序设计结构(1)
- 设计一个DNS的Cache结构,要求能够满足每秒5000以上的查询,满足IP数据的快速插入,查询的速度要快
- 数据结构之单链表的基本操作
- 数据仓库专题22-网络用户行为数据采集标准规范设计
- 数据结构——基本数据结构之队列
- 数据结构之树和二叉树---二叉树的基本操作
- 数据结构——双向链表实现,基本操作的C++版
- 数据结构——课程设计
- 数据结构基本结构 数据结构期末复习
- 数据结构拾遗(3) --红黑树的设计与实现(下)
- 数据结构基础(7) --循环队列的设计与实现
- 实战数据结构(5)_双向循环链表的基本操作
- Go在谷歌:以软件工程为目的的语言设计