首页 文章资讯内容详情

Golang之Context的使用

2026-06-01 3 花语

本文内容纲要:

- -官方案例 转载自:http://www.nljb.net/default/Golang%E4%B9%8BContext%E7%9A%84%E4%BD%BF%E7%94%A8/ 简介 在golang中的创建一个新的线程并不会返回像c语言类似的pid 所有我们不能从外部杀死某个线程,所有我就得让它自己结束 之前我们用channel+select的方式,来解决这个问题 但是有些场景实现起来比较麻烦,例如由一个请求衍生出多个线程 并且之间需要满足一定的约束关系,以实现一些诸如: 有效期,中止线程树,传递请求全局变量之类的功能。 于是google就为我们提供一个解决方案,开源了context包。 使用context包来实现上下文功能..... 约定:需要在你的方法的传入参数的第一个参数是context.Context的变量。

其实本身非常简单,在导入这个包之后,初始化Context对象,在每个资源访问方法中都调用它,然后在使用时检查Context对象是否已经被Cancel,如果是就释放绑定的资源

源码剖析

context.Context接口

context包里的方法是线程安全的,可以被多个线程使用

当Context被canceled或是timeout,Done返回一个被closed的channel

在Done的channel被closed后,Err代表被关闭的原因

如果存在,Deadline返回Context将要关闭的时间

如果存在,Value返回与key相关了的值,不存在返回nil

//context包的核心 typeContextinterface{

Done()<-chanstruct{}

Err()error Deadline()(deadlinetime.Time,okbool) Value(keyinterface{})interface{} }

我们不需要手动实现这个接口,context包已经给我们提供了两个

一个是Background(),一个是TODO() 这两个函数都会返回一个Context的实例 只是返回的这两个实例都是空Context。 /* TODO返回一个非空,空的上下文 在目前还不清楚要使用的上下文或尚不可用时 */ context.TODO() /* Background返回一个非空,空的上下文。 这是没有取消,没有值,并且没有期限。 它通常用于由主功能,初始化和测试,并作为输入的顶层上下文 */ context.Background() 主要方法 funcWithCancel(parentContext)(ctxContext,cancelCancelFunc) funcWithDeadline(parentContext,deadlinetime.Time)(Context,CancelFunc) funcWithTimeout(parentContext,timeouttime.Duration)(Context,CancelFunc) funcWithValue(parentContext,keyinterface{},valinterface{})Context

WithCancel对应的是cancelCtx,其中,返回一个cancelCtx,同时返回一个CancelFunc,CancelFunc是context包中定义的一个函数类型:typeCancelFuncfunc()。调用这个CancelFunc时,关闭对应的c.done,也就是让他的后代goroutine退出

WithDeadline和WithTimeout对应的是timerCtx,WithDeadline和WithTimeout是相似的,WithDeadline是设置具体的deadline时间,到达deadline的时候,后代goroutine退出,而WithTimeout简单粗暴,直接returnWithDeadline(parent,time.Now().Add(timeout))

WithValue对应valueCtx,WithValue是在Context中设置一个map,拿到这个Context以及它的后代的goroutine都可以拿到map里的值

context的创建

所有的context的父对象,也叫根对象,是一个空的context,它不能被取消,它没有值,从不会被取消,也没有超时时间,它常常作为处理request的顶层context存在,然后通过WithCancel、WithTimeout函数来创建子对象来获得cancel、timeout的能力

当顶层的request请求函数结束后,我们就可以cancel掉某个context,从而通知别的routine结束

WithValue方法可以把键值对加入context中,让不同的routine获取

官方案例

//在handle环境中使用 funchandleSearch(whttp.ResponseWriter,req*http.Request){ //ctxistheContextforthishandler.Callingcancelclosesthe //ctx.Donechannel,whichisthecancellationsignalforrequests //startedbythishandler. var( ctxcontext.Context cancelcontext.CancelFunc ) //获取参数... timeout,err:=time.ParseDuration(req.FormValue("timeout")) iferr==nil{ //Therequesthasatimeout,socreateacontextthatis //canceledautomaticallywhenthetimeoutexpires. //获取成功,则按照参数设置超时时间 ctx,cancel=context.WithTimeout(context.Background(),timeout) }else{ //获取失败,则在该函数结束时结束... ctx,cancel=context.WithCancel(context.Background()) } //---------------- //这样随着cancel的执行,所有的线程都随之结束了... goA(ctx)+1 goB(ctx)+2 goC(ctx)+3 //---------------- defercancel()//CancelctxassoonashandleSearchreturns. } //监听ctx.Done()结束... funcA(ctxcontext.Context)int{ //...TODO select{ case<-ctx.Done(): return-1 default: //没有结束...执行... } } ctxhttp.go packagectxhttp//import"golang.org/x/net/context/ctxhttp" import( "io" "net/http" "net/url" "strings" "golang.org/x/net/context" ) //DosendsanHTTPrequestwiththeprovidedhttp.Clientandreturns //anHTTPresponse. // //Iftheclientisnil,http.DefaultClientisused. // //Theprovidedctxmustbenon-nil.Ifitiscanceledortimesout, //ctx.Err()willbereturned. funcDo(ctxcontext.Context,client*http.Client,req*http.Request)(*http.Response,error){ ifclient==nil{ client=http.DefaultClient } resp,err:=client.Do(req.WithContext(ctx)) //Ifwegotanerror,andthecontexthasbeencanceled, //thecontextserrorisprobablymoreuseful. iferr!=nil{ select{ case<-ctx.Done(): err=ctx.Err() default: } } returnresp,err } //GetissuesaGETrequestviatheDofunction. funcGet(ctxcontext.Context,client*http.Client,urlstring)(*http.Response,error){ req,err:=http.NewRequest("GET",url,nil) iferr!=nil{ returnnil,err } returnDo(ctx,client,req) } //HeadissuesaHEADrequestviatheDofunction. funcHead(ctxcontext.Context,client*http.Client,urlstring)(*http.Response,error){ req,err:=http.NewRequest("HEAD",url,nil) iferr!=nil{ returnnil,err } returnDo(ctx,client,req) } //PostissuesaPOSTrequestviatheDofunction. funcPost(ctxcontext.Context,client*http.Client,urlstring,bodyTypestring,bodyio.Reader)(*http.Response,error){ req,err:=http.NewRequest("POST",url,body) iferr!=nil{ returnnil,err } req.Header.Set("Content-Type",bodyType) returnDo(ctx,client,req) } //PostFormissuesaPOSTrequestviatheDofunction. funcPostForm(ctxcontext.Context,client*http.Client,urlstring,dataurl.Values)(*http.Response,error){ returnPost(ctx,client,url,"application/x-www-form-urlencoded",strings.NewReader(data.Encode())) } 使用示例 packagemain import( "fmt" "time" "golang.org/x/net/context" ) funcCdd(ctxcontext.Context)int{ fmt.Println(ctx.Value("NLJB")) select{ //结束时候做点什么... case<-ctx.Done(): return-3 default: //没有结束...执行... } } funcBdd(ctxcontext.Context)int{ fmt.Println(ctx.Value("HELLO")) fmt.Println(ctx.Value("WROLD")) ctx=context.WithValue(ctx,"NLJB","NULIJIABEI") gofmt.Println(Cdd(ctx)) select{ //结束时候做点什么... case<-ctx.Done(): return-2 default: //没有结束...执行... } } funcAdd(ctxcontext.Context)int{ ctx=context.WithValue(ctx,"HELLO","WROLD") ctx=context.WithValue(ctx,"WROLD","HELLO") gofmt.Println(Bdd(ctx)) select{ //结束时候做点什么... case<-ctx.Done(): return-1 default: //没有结束...执行... } } funcmain(){ //自动取消(定时取消) { timeout:=3*time.Second ctx,_:=context.WithTimeout(context.Background(),timeout) fmt.Println(Add(ctx)) } //手动取消 //{ //ctx,cancel:=context.WithCancel(context.Background()) //gofunc(){ //time.Sleep(2*time.Second) //cancel()//在调用处主动取消 //}() //fmt.Println(Add(ctx)) //} select{} } packagemain import( "fmt" "time" "golang.org/x/net/context" ) //模拟一个最小执行时间的阻塞函数 funcinc(aint)int{ res:=a+1//虽然我只做了一次简单的+1的运算, time.Sleep(1*time.Second)//但是由于我的机器指令集中没有这条指令, //所以在我执行了1000000000条机器指令,续了1s之后,我才终于得到结果。B) returnres } //向外部提供的阻塞接口 //计算a+b,注意a,b均不能为负 //如果计算被中断,则返回-1 funcAdd(ctxcontext.Context,a,bint)int{ res:=0 fori:=0;i<a;i++{ res=inc(res) select{ case<-ctx.Done(): return-1 default: //没有结束...执行... } } fori:=0;i<b;i++{ res=inc(res) select{ case<-ctx.Done(): return-1 default: //没有结束...执行... } } returnres } funcmain(){ { //使用开放的API计算a+b a:=1 b:=2 timeout:=2*time.Second ctx,_:=context.WithTimeout(context.Background(),timeout) res:=Add(ctx,1,2) fmt.Printf("Compute:%d+%d,result:%d\n",a,b,res) } { //手动取消 a:=1 b:=2 ctx,cancel:=context.WithCancel(context.Background()) gofunc(){ time.Sleep(2*time.Second) cancel()//在调用处主动取消 }() res:=Add(ctx,1,2) fmt.Printf("Compute:%d+%d,result:%d\n",a,b,res) } }

本文内容总结:,官方案例,

原文链接:https://www.cnblogs.com/tianlongtc/p/8824740.html