在并发程序中,由于超时、取消操作或其他一些异常情况,往往需要通知其他goroutine,虽然可以使用channel来处理这些问题,但是会变得非常繁琐,而且不利于多级管理。 go使用Context来做解决方案。1、Context接口typeContextinterface{Deadline()(deadlinetime。Time,okbool)Done()chanstruct{}Err()errorValue(keyinterface{})interface{}} Context接口包含4个方法Deadline:返回绑定当前Context的任务被取消的截止时间,如果没有设置时间,okfalseDone:context任务被取消,返回一个信号struct{},如果不被取消,返回nilErr:如果Done已经关闭,将返回非空的值表明任务结束的原因Value:存储的键值对中当前key对应的值2、emptyCtx emptyCtx其实就是一个int类型的变量,实现了Context接口。 如其名,就是一个没有设置超时时间,不能取消,也不能存储键值对的Context。 emptyCtx用来作为context的根结点。typeemptyCtxintfunc(emptyCtx)Deadline()(deadlinetime。Time,okbool){return}func(emptyCtx)Done()chanstruct{}{returnnil}func(emptyCtx)Err()error{returnnil}func(emptyCtx)Value(keyinterface{})interface{}{returnnil} 而我们通常不会直接使用emptyCtx,而是使用emptyCtx实例化的两个变量var(backgroundnew(emptyCtx)todonew(emptyCtx))funcBackground()Context{returnbackground}funcTODO()Context{returntodo}Background:通常被用作主函数,初始化以及测试中,作为顶级的contextTODO:不确定使用什么context时3、valueCtx3。1、基础类型typevalueCtxstruct{Contextkey,valinterface{}}func(cvalueCtx)Value(keyinterface{})interface{}{ifc。keykey{returnc。val}returnc。Context。Value(key)} valueCtx利用了context类型的变量来表示父节点context,继承了父context的所有信息。 valueCtx携带了一个键值对,实现了Value方法,所以可以在context上获取key对应的值,如果context不存在,会沿着父context向上查找3。2、实现方法funcWithValue(parentContext,key,valinterface{})Context{ifkeynil{panic(nilkey)}if!reflectlite。TypeOf(key)。Comparable(){panic(keyisnotcomparable)}returnvalueCtx{parent,key,val}} 向context中添加键值对,并不是直接在原context上直接添加,而是创建一个新的valueCtx,将键值对添加在子节点上。4、cancelCtx4。1、基础类型typecancelerinterface{cancel(removeFromParentbool,errerror)Done()chanstruct{}}typecancelCtxstruct{Contextmusync。Mutexdonechanstruct{}childrenmap〔canceler〕struct{}errerror} 和valueCtx类似,也有父context,通道done用来传递关闭信号。children存储了context节点下的子节点,err用于存储取消原因func(ccancelCtx)Value(keyinterface{})interface{}{ifkeycancelCtxKey{returnc}returnc。Context。Value(key)}func(ccancelCtx)Done()chanstruct{}{c。mu。Lock()ifc。donenil{c。donemake(chanstruct{})}d:c。donec。mu。Unlock()returnd}func(ccancelCtx)Err()error{c。mu。Lock()err:c。errc。mu。Unlock()returnerr}4。2、实现方法funcWithCancel(parentContext)(ctxContext,cancelCancelFunc){c:newCancelCtx(parent)propagateCancel(parent,c)returnc,func(){c。cancel(true,Canceled)}} newCancelCtx只是初始化了cancelCtxfuncnewCancelCtx(parentContext)cancelCtx{returncancelCtx{Context:parent}} propagateCancel建立当前节点与父节点的取消逻辑funcpropagateCancel(parentContext,childcanceler){done:parent。Done()ifdonenil{return}select{casedone:child。cancel(false,parent。Err())returndefault:}ifp,ok:parentCancelCtx(parent);ok{p。mu。Lock()ifp。err!nil{child。cancel(false,p。err)}else{ifp。childrennil{p。childrenmake(map〔canceler〕struct{})}p。children〔child〕struct{}{}}p。mu。Unlock()}else{atomic。AddInt32(goroutines,1)gofunc(){select{caseparent。Done():child。cancel(false,parent。Err())casechild。Done():}}()}} 1、如果父context已经取消了,就直接返回,因为父节点不可能再被取消了 2、监听信号done,如果接收到了就通知子context取消 3、如果找到父context,就挂在父context上 4、如果没有找到父context,也就是自身是根context,就启动一个goroutine监听信号 而调用的cancel方法,其实就是关闭通道及设置原因func(ccancelCtx)cancel(removeFromParentbool,errerror){iferrnil{panic(context:internalerror:missingcancelerror)}c。mu。Lock()ifc。err!nil{c。mu。Unlock()returnalreadycanceled}c。errerrifc。donenil{c。doneclosedchan}else{close(c。done)}forchild:rangec。children{child。cancel(false,err)}c。childrennilc。mu。Unlock()ifremoveFromParent{removeChild(c。Context,c)}}5、timerCtx5。1、基础类型typetimerCtxstruct{cancelCtxtimertime。Timerdeadlinetime。Time}func(ctimerCtx)Deadline()(deadlinetime。Time,okbool){returnc。deadline,true} timer声明了一个定时器,用于发送截止时间5。2、实现方法funcWithDeadline(parentContext,dtime。Time)(Context,CancelFunc){ifcur,ok:parent。Deadline();okcur。Before(d){returnWithCancel(parent)}c:timerCtx{cancelCtx:newCancelCtx(parent),deadline:d,}propagateCancel(parent,c)dur:time。Until(d)ifdur0{c。cancel(true,DeadlineExceeded)returnc,func(){c。cancel(false,Canceled)}}c。mu。Lock()deferc。mu。Unlock()ifc。errnil{c。timertime。AfterFunc(dur,func(){c。cancel(true,DeadlineExceeded)})}returnc,func(){c。cancel(true,Canceled)}} 大致和cancelCtx差不多,多了声明的定时器,用于发送截止时间。 而timerCtx。cancel有些不一样,是关闭定时器的。func(ctimerCtx)cancel(removeFromParentbool,errerror){c。cancelCtx。cancel(false,err)ifremoveFromParent{removeChild(c。cancelCtx。Context,c)}c。mu。Lock()ifc。timer!nil{c。timer。Stop()c。timernil}c。mu。Unlock()} 关于timerCtx还有一个方法funcWithTimeout(parentContext,timeouttime。Duration)(Context,CancelFunc){returnWithDeadline(parent,time。Now()。Add(timeout))} 与WithDeadline类似,只不过是创建了一个过期时间的context6、总结context主要用于父子之间同步信号,本质上是一种协程调度方式context是线程安全的,因为context本身不变父context通知子context取消,但是不会干涉子任务的执行,也就是说context的取消机制是无侵入的子context的取消是不会影响父context的