3//如果Context实现了超时控制,该方法返回 超时时间,true。否则ok为false
4Done() <- chanstruct{}
5//依旧使用<-chan struct{}来通知退出,供被调用的goroutine监听。
6Err() error
7//当Done()返回的chan收到通知后,防卫Err()获知被取消的原因
8Value(key interface{}) interface
9}
canceler接口:拓展接口,规定了取消通知的Context具体类型需要实现的接口:
1typecanceler interface{
2cancel(removeFromParent bool, err error)
3//通知后续创建的goroutine退出
4Done() <- chanstruct{}
5//作者对这个Done()方法的理解是多余的
6}
struct
emptyCtx:实现了一个不具备任何功能的Context接口,其存在的目的就是作为Context对象树的root节点:
1typeemptyCtx int
2func(*emptyCtx)Deadline()(deadline time.Time, ok bool){
3return
4}
5func(*emptyCtx)Done()<-chanstruct{} {
6returnnil
7}
8func(*emptyCtx)Err()error{
9returnnil
10}
11func(*emptyCtx)Value(key interface{})interface{} {
12returnnil
13}
14//......
15var(
16background = new(emptyCtx)
17todo = new(emptyCtx)
18)
19funcBackground()Context{
20returnbackground
21}
22funcTODO()Context{
23returntodo
24}
25//这两者返回值是一样的,文档上建议main函数可以使用Background()创建root context
cancelCtx:可以认为他与emptyCtx最大的区别在于,具体实现了cancel函数。即他可以向子goroutine传递cancel消息。
timerCtx:另一个实现Context接口的具体类型,内部封装了cancelCtx类型实例,同时拥有deadline变量,用于实现定时退出通知。
valueCtx:实现了Context接口的具体类型,内部分装cancelCtx类型实例,同时封装了一个kv存储变量,用于传递通知消息。
API
除了root context可以使用Background()创建以外,其余的context都应该从cancelCtx,timerCtx,valueCtx中选取一个来构建具体对象:
func WithCancel(parent Context) (Context, CancelFunc):创建cancelCtx实例。
func WithDeadline(parent Context, deadline time.Time)(Context, CancelFunc)与func WithTimeout(parent Context, timeout time.Duration)(Context, CancelFunc):两种方法都可以创建一个带有超时通知的Context具体对象timerCtx,具体差别在于传递绝对或相对时间。
func WithValue(parent Context, key, val interface{}) Context:创建valueCtx实例。
1、创建root context并构建一个WithCancel类型的上下文,使用该上下文注册一个goroutine模拟运行:
1funcmain(){
2ctxa, cancel := context.WithCancel(context.Background())
3gowork(ctxa, "work1")
4}
5funcwork(ctx context.Context, name string){
6for{
7select{
8case<-ctx.Done():
9println(name, " get message to quit")
10return
11default:
12println(name, " is running")
13time.Sleep(time.Second)
14}
15}
16}
2、使用WithDeadline包装ctxa,并使用新的上下文注册另一个goroutine:
1funcmain(){
2ctxb, _ := context.WithTimeout(ctxa, time.Second * 3)
3gowork(ctxb, "work2")
4}
3、使用WithValue包装ctxb,并注册新的goroutine:
1funcmain(){
2ctxc := context.WithValue(ctxb, "key", "custom value")
3goworkWithValue(ctxc, "work3")
4}
5funcworkWithValue(ctx context.Context, name string){
6for{
7select{
8case<-ctx.Done():
9println(name, " get message to quit")
10return
11default:
12value:=ctx.Value( "key").( string)
13println(name, " is running with value", value)
14time.Sleep(time.Second)
15}
16}
17}
4、最后在main函数中手动关闭ctxa,并等待输出结果:
1func main(){
2time.Sleep( 5*time.Second)
3cancel()
4time.Sleep(time.Second)
5}
6
7至此我们运行程序并查看输出结果:
8work1 isrunning
9work3 isrunning with valuecustom value
10work2 isrunning
11work1 isrunning
12work2 isrunning
13work3 isrunning with valuecustom value
14work2 isrunning
15work3 isrunning with valuecustom value
16work1 isrunning
17//work2超时并通知work3退出
18work2 getmessage to quit
19work3 getmessage to quit
20work1 isrunning
21work1 isrunning
22work1 getmessage to quit
可以看到,当ctxb因超时而退出之后,会通知由他包装的所有子goroutine(ctxc),并通知退出。各context的关系结构如下:
Background() -> ctxa -> ctxb -> ctxc
Background() -> ctxa -> ctxb -> ctxc
我们主要研究两个问题,即各Context如何保存父类和子类上下文;以及cancel方法如何实现通知子类context实现退出功能。
context的数据结构
1、emptyCtx只是一个uint类型的变量,其目的只是为了作为第一个goroutine ctx的parent,因此他不需要,也没法保存子类上下文结构。
2、cancelCtx的数据结构:
1type cancelCtx struct{
2Context
3
4mu sync.Mutex // protects following fields
5done chan struct{} // created lazily, closed by first cancel call
6children map[canceler] struct{} // set to nil by the first cancel call
7err error // set to non-nil by the first cancel call
8}
Context接口保存的就是父类的context。children map[canceler]struct{}保存的是所有直属与这个context的子类context。done chan struct{}用于发送退出信号。
我们查看创建cancelCtx的APIfunc WithCancel(…)…:
1funcWithCancel(parent Context)(ctx Context, cancel CancelFunc){
2c := newCancelCtx(parent)
3propagateCancel(parent, &c)
4return&c, func(){ c.cancel( true, Canceled) }
5}
6funcnewCancelCtx(parent Context)cancelCtx{
7returncancelCtx{Context: parent}
8}
propagateCancel函数的作用是将自己注册至parent context。我们稍后会讲解这个函数。
3、timerCtx的数据结构:
1type timerCtx struct{
2cancelCtx
3timer *time.Timer // Under cancelCtx.mu.
4
5deadline time.Time
6}
timerCtx继承于cancelCtx,并为定时退出功能新增自己的数据结构。
1funcWithDeadline(parent Context, d time.Time)(Context, CancelFunc){
2ifcur, ok := parent.Deadline(); ok && cur.Before(d) {
3// The current deadline is already sooner than the new one.
4returnWithCancel(parent)
5}
6c := &timerCtx{
7cancelCtx: newCancelCtx(parent),
8deadline: d,
9}
10propagateCancel(parent, c)
11//以下内容与定时退出机制有关,在本文不作过多分析和解释
12dur := time.Until(d)
13ifdur <= 0{
14c.cancel( true, DeadlineExceeded) // deadline has already passed
15returnc, func(){ c.cancel( true, Canceled) }
16}
17c.mu.Lock()
18deferc.mu.Unlock()
19ifc.err == nil{
20c.timer = time.AfterFunc(dur, func(){
21c.cancel( true, DeadlineExceeded)
22})
23}
24returnc, func(){ c.cancel( true, Canceled) }
25}
26funcnewCancelCtx(parent Context)cancelCtx{
27returncancelCtx{Context: parent}
28}
timerCtx查看parent context的方法是timerCtx.cancelCtx.Context。
4、valueCtx的数据结构:
1type valueCtx struct{
2Context
3key, val interface{}
4}
相较于timerCtx而言非常简单,没有继承于cancelCtx struct,而是直接继承于Context接口。
1funcWithValue(parent Context, key, val interface{})Context{
2ifkey == nil{
3panic( "nil key")
4}
5if!reflect.TypeOf(key).Comparable() {
6panic( "key is not comparable")
7}
8return&valueCtx{parent, key, val}
9}
辅助函数
这里我们会有两个疑问,第一,valueCtx为什么没有propagateCancel函数向parent context注册自己。既然没有注册,为何ctxb超时后能通知ctxc一起退出。第二,valueCtx是如何存储children和parent context结构的。相较于同样绑定Context接口的cancelCtx,valueCtx并没有children数据。
第二个问题能解决一半第一个问题,即为何不向parent context注册。先说结论:valueCtx的children context注册在valueCtx的parent context上。函数func propagateCancel(…)负责注册信息,我们先看一下他的构造:
func propagateCancel1funcpropagateCancel(parent Context, child canceler){
2ifparent.Done() == nil{
3return// parent is never canceled
4}
5ifp, ok := parentCancelCtx(parent); ok {
6p.mu.Lock()
7ifp.err != nil{
8// parent has already been canceled
9child.cancel( false, p.err)
10} else{
11ifp.children == nil{
12p.children = make( map[canceler] struct{})
13}
14p.children[child] = struct{}{}
15}
16p.mu.Unlock()
17} else{
18gofunc(){
19select{
20case<-parent.Done():
21child.cancel( false, parent.Err())
22case<-child.Done():
23}
24}()
25}
26}
这个函数的主要逻辑如下:接收parent context 和 child canceler方法,若parent为emptyCtx,则不注册;否则通过funcparentCancelCtx寻找最近的一个*cancelCtx;若该cancelCtx已经结束,则调用child的cancel方法,否则向该cancelCtx注册child。
func parentCancelCtx1funcparentCancelCtx(parent Context)(*cancelCtx, bool){
2for{
3switchc := parent.( type) {
4case*cancelCtx:
5returnc, true
6case*timerCtx:
7return&c.cancelCtx, true
8case*valueCtx:
9parent = c.Context
10default:
11returnnil, false
12}
13}
14}
func parentCancelCtx从parentCtx中向上迭代寻找第一个cancelCtx并返回。从函数逻辑中可以看到,只有当parent.(type)为*valueCtx的时候,parent才会向上迭代而不是立即返回。否则该函数都是直接返回或返回经过包装的*cancelCtx。因此我们可以发现,valueCtx是依赖于parentCtx的*cancelCtx结构的。
至于第二个问题,事实上,parentCtx根本无需,也没有办法通过Done()方法通知valueCtx,valueCtx也没有额外实现Done()方法。可以理解为:valueCtx与parentCtx公用一个done channel,当parentCtx调用了cancel方法并关闭了done channel时,监听valueCtx的done channel的goroutine同样会收到退出信号。另外,当parentCtx没有实现cancel方法(如emptyCtx)时,可以认为valueCtx也是无法cancel的。
func (c *cancelCtx) cancel1func(c *cancelCtx)cancel(removeFromParent bool, err error){
2iferr == nil{
3panic( "context: internal error: missing cancel error")
4}
5c.mu.Lock()
6ifc.err != nil{
7c.mu.Unlock()
8return// already canceled
9}
10c.err = err
11ifc.done == nil{
12c.done = closedchan
13} else{
14close(c.done)
15}
16forchild := rangec.children {
17child.cancel( false, err)
18}
19c.children = nil
20c.mu.Unlock()
21
22ifremoveFromParent {
23removeChild(c.Context, c)
24}
25}
该方法的主要逻辑如下:若外部err为空,则代表这是一个非法的cancel操作,抛出panic;若cancelCtx内部err不为空,说明该Ctx已经执行过cancel操作,直接返回;关闭done channel,关联该Ctx的goroutine收到退出通知;遍历children,若有的话,执行child.cancel操作;调用removeChild将自己从parent context中移除。
func (c *timerCtx) cancel
与cancelCtx十分类似,不作过多阐述。
ID:Golangweb