context的四个基本方法
1.背景介绍
很多时候,我们会遇到上下go routine需要同时取消的情况,这就涉及到go routine之间的通信。在围棋中,建议我们通过通信共享内存,而不是共享内存。
因此,我们需要使用channl。但是,在上面的场景中,如果需要我们自己处理channl的业务逻辑,就会有大量费时费力的重复性工作。因此,出现了上下文。
它是一种在上下文中进行进程通信的方式,其底层借助channl和snyc来实现。互斥(体)…
2.基本介绍
context的底层设计可以概括为一个接口、四种实现和六种方法。
1个接口
上下文指定了上下文的四种基本方法。
四种实现
EmptyCtx实现了一个空上下文,可以用作根节点。
CancelCtx实现了一个带有取消功能的上下文,可以主动取消。
TimerCtx实现了一个由定时器timer和deadline取消的上下文。
valueCtx实现了一个上下文,在该上下文中可以通过键和值字段存储数据。
六种方法
后台返回一个emptyCtx作为根节点。
TODO将emptyCtx作为未知节点返回
WithCancel返回一个cancelCtx。
WithDeadline返回一个timerCtx。
WithTimeout返回一个timerCtx。
With返回valueCtx。
3.源代码分析
3.1上下文接口
类型上下文接口{ Deadline()(截止时间。Time,ok bool) Done() -chan struct{} Err()错误值(键接口{})接口{}}
Deadline():返回一个时间。时间,表示当前上下文应该结束的时间,ok表示有结束时间。
Done():返回一个只读的chan。如果可以从该chan中读取数据,则ctx已被取消。
Err():返回取消上下文的原因。
Value(key):返回与密钥相对应的值,该值是协议安全的。
3.2 emptyCtx
键入emptyCtx int func(* emptyCtx)Deadline()(截止时间。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 }
EmptyCtx实现了一个空的Context接口,它的主要作用是为background和todo方法返回预先初始化的私有变量Background和TODO,这些变量将在同一个Go程序中重用:
var(Background=new(emptyCtx)TODO=new(emptyCtx))func Background()Context { return Background } func TODO()Context { return TODO }
后台和TODO在实现上没有区别,只是使用的语义不同:
背景是上下文的根节点;
O TODO只应在您不确定应使用哪个上下文时使用;
3.3取消tx
CancelCtx实现了取消器接口和上下文接口:
类型取消器接口{ cancel(removefrompertanchor bool,err error) Done() -chan struct{}}
其结构如下:
类型cancelCtx struct {//直接嵌入一个上下文,那么cancelCtx可以看作是一个上下文上下文musync.mutex//protections以下字段完成atomic.value//of chan struct { },惰性创建,通过第一次取消调用关闭子映射[canceler]struct{} //通过第一次取消调用设置为nil err error//通过第一次取消调用设置为非nil }
我们可以使用WithCancel方法来创建cancelCtx:
func with cancel(parent Context)(CTX Context,cancel cancel func){ if parent==nil { panic(无法从nil父级创建上下文)} c:=newCancelCtx(parent)propagate cancel(parent,c) return c,func() { c.cancel(true,Canceled)} } func newCancelCtx(parent Context)cancel CTX { return cancel CTX { Context:parent } }
上面的方法,我们传入一个父context(这通常是一个后台,作为根节点),返回新创建的Context,以闭包的形式返回一个cancel方法。
NewCancelCtx将传入的上下文包装到一个私有结构context.cancelCtx中
PropagateCancel构建父上下文和子上下文之间的关系,以形成一个树形结构。当父上下文被取消时,子上下文也将被取消:
c传播取消(父上下文,子取消器){//1。如果父ctx是不可撤销的ctx,那么直接返回,不进行关联done:=parent . done()if done==nil { return//parent永不取消}//2。然后,判断父ctx是否已经取消。选择{case -done: //2.1如果上级ctx已经取消,则不需要关联。//那么这里也应该顺便取消子ctx,因为父ctx应该取消子ctx。//这里需要手动触发取消,因为还没有关联。//父级已经是取消的子级。Cancel (false,Parent.err())返回默认值:}//3。从父Ctx中提取cancelCtx,并将子ctx添加到父ctx的子ctx if p,ok:=parentCancelCtx(parent);Ok {p.mu.Lock() //仔细检查确认父ctx是否已经取消如果p.err!=nil {//直接取消当前ctx。//父级已经被取消子级。Cancel (false,P.err)} else {//否则,将其添加到children if p . children==nil { p . children=make(map[canceler]struct { })} p . children[child]=struct { } { } p . mu . unlock()} else。开始一个新的取消信号atomic.addint32 (goroutines,1)go func(){ select { case-parent . done():child . cancel(false,parent . err())case-child . done():} }()}
上述方法可能会遇到以下情况:
当父母。Done()==nil,即parent不会触发取消事件,当前函数直接返回;
当child的继承链包含可取消的上下文时,它将确定父级是否触发了取消信号;
如果已经取消,孩子将立即被取消;
如果不取消,孩子会被添加到父节点的孩子列表中,等待父节点释放取消信号;
当父上下文是开发人员定义的类型时,实现该上下文。上下文接口,并在Done()方法中返回一个非空管道;
运行新的Goroutine来收听两个频道;父母。Done()和child。done();
调用child.cancel以取消父上下文。Done()已关闭;
propagateCancel的作用是同步父节点和子节点之间的取消和结束信号,保证父节点被取消时,子节点也会收到相应的信号,不会出现不一致的状态。
func parentCancelCtx(父上下文)(*cancelCtx,Bool) {done :=parent。Done() //如果Done表示这个ctx对于nil是可取消的//如果done==closedchan表示这个ctx不是标准的cancelCtx,也许它一个自定义if done==closed chan | | done==nil { return nil,false }//然后调用value方法从ctx中提取cancelctxp,ok:=parent . value(cancel txkey)。(* cancelctx)如果!Ok {return nil,false} //最后判断cancelCtx中存储的done与父Ctx中的done是否一致。//如果不是,则父级不是CancelCTX P done,_:=p.done.load()。(chan struct {}) if p done!=done { return nil,false } return p,true}
AncelCtx 的done方法返回一个更改结构{}:
func(c * cancel CTX)Done()-chan struct { } { d:=c . Done . load()if d!=nil { return d .(chan struct { })} c .lock()defer c .unlock()d=c . done . load()if d==nil { d=make(chan struct { })c . done . store(d)} return d .(chan struct { })} var closed chan=make(chan struct { })
ParentCancelCtx实际上是判断父上下文中是否有CancelCtx,如果有就返回,这样子上下文就可以锚定到父上下文。如果不是,它将返回false,不锚定,并打开一个新的goroutine来监视它。
3.4定时器rCtx
TimerCtx不仅通过嵌入cancelCtx继承了相关的变量和方法,还通过持有timer timer和deadline实现了定时取消的功能:
键入timer CTX struct { cancel CTX timer * time。timer//Under cancel CTX截止时间。Time}func (c *timerCtx) Deadline()(截止时间。Time,ok bool) { return c.deadline,true } func(c * timerCtx)cancel(removefromparten bool,err error){ c . cancel tx . cancel(false,err)if removefromparten { remove child(c . cancel CTX . context,c)} c . mu lock()if c . timer!=nil { c . timer . stop()c . timer=nil } cunlock()}
3.5价值Ctx
valueCtx有两个额外的字段key和val来存储数据:
type valueCtx struct {上下文键,val接口{}}
值搜索的过程实际上是一个递归搜索过程:
func(c * Value CTX)Value(key interface { })interface { } { if c . key==key { return c . val } return c . context . Value(key)}
如果键与当前ctx中存储的值一致,则直接返回;如果没有,将在父代中找到。最后找到根节点(通常是emptyCtx)直接返回一个nil。所以在使用Value方法时,需要判断结果是否为nil,类似于链表,效率很低。不建议传递参数。
使用建议
在官方博客中,提出了一些使用语境的建议:
唐不要把上下文塞进结构中。上下文类型直接作为函数的第一个参数,一般命名为ctx。
唐不要向函数传递一个nil上下文。如果你真的不不知道传递什么,标准库已经为你准备了一个上下文:TODO。
唐不要将应该用作函数参数的类型插入到上下文中。上下文应该存储一些公共数据。例如:登录会话、cookie等。
相同的上下文可以传递给多个goroutine。唐别担心,上下文是并发安全的。
审计彭静
推荐阅读
- 连力推出配备第8代Asetek泵和2.88英寸IPS屏幕的Galahad II LCD LCS
- 盖伦上单出装符文天赋,LOLs6盖伦上单天赋符文加点图
- 诺基亚X50 Pro概念机曝光,一亿像素+6000mAh,还有120Hz高刷(诺基亚x50pro多少钱)
- 看望病人应该买些什么,说些什么
- 吕方,中国香港歌手、演员
- oppor11s怎样解锁图案密码,OPPOR11屏幕锁或图案锁忘记了怎么解锁
- 为何火焰玻璃一直刷不出来,火焰玻璃怎么得
- 英国伦敦奥运会的标志有哪些 历届夏季奥运会、冬季奥运会标识
- 阿森纳2:0拜仁慕尼黑,阿森纳 vs 拜仁慕尼黑比分结果和历史战绩
- 奇瑞旗云2保养灯归零复位(带图教程),11年奇瑞旗云2保养灯怎么手动归零
- 女生谈恋爱最后的底线(女孩子谈恋爱,这四个底线别碰)
- 3万左右的新车有哪些(3万一4万新车小车)
- 什么是蚜虫,蚜虫是如何产生的
- 苹果无法打开appstore,苹果设备打不开appstore的解决方法
- 王者荣耀出师的徒弟还给名师点么,王者荣耀怎么出师
- 截止到20218月,性价比最高的苹果手机有哪些型号。?(苹果手机价格一览表)
- 黄品源,中国台湾男歌手、演员、主持人
- 乌拉圭现役球星 那不勒斯队史十大代表球员
- 电脑桌面图标不显示怎么办,电脑桌面图标不见或桌面无图标只显示壁纸