Golang之Context的使用
本文内容纲要:
-
-官方案例
转载自: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,
//thecontext'serrorisprobablymoreuseful.
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