golang中context的作用详解
当一个goroutine可以启动其他goroutine,而这些goroutine可以启动其他goroutine,依此类推,则第一个goroutine应该能够向所有其它goroutine发送取消信号。
上下文包的唯一目的是在goroutine之间执行取消信号,而不管它们如何生成。上下文的接口定义为:
typeContextinterface{
Deadline()(deadlinetime.Time,okbool)
Done()<-chanstruct{}
Err()error
Value(keyinterface{})interface{}
}
- Deadline:第一个值是截止日期,此时上下文将自动触发“取消”操作。第二个值是布尔值,true表示设置了截止日期,false表示未设置截止时间。如果没有设置截止日期,则必须手动调用cancel函数来取消上下文。
- Done:返回一个只读通道(仅在取消后),键入struct{},当该通道可读时,表示父上下文已经发起了取消请求,根据此信号,开发人员可以执行一些清除操作,退出goroutine
- Err:返回取消上下文的原因
- Value:返回绑定到上下文的值,它是一个键值对,因此您需要传递一个Key来获取相应的值,此值是线程安全的
要创建上下文,必须指定父上下文。两个内置上下文(背景和待办事项)用作顶级父上下文:
var(
background=new(emptyCtx)
todo=new(emptyCtx)
)
funcBackground()Context{
returnbackground
}
funcTODO()Context{
returntodo
}
背景,主要ü在主函数,初始化和测试代码的sed,是树结构中,根上下文,这是不能被取消的顶层语境。TODO,当您不知道要使用什么上下文时,可以使用它。它们本质上都是emptyCtx类型,都是不可取消的,没有固定的期限,也没有为Context赋任何值:键入emptyCtxint
typeemptyCtxint
func(_*emptyCtx)Deadline()(deadlinetime.Time,okbool){
return
}
func(_*emptyCtx)Done()<-chanstruct{}{
returnnil
}
func(_*emptyCtx)Err()error{
returnnil
}
func(*emptyCtx)Value(keyinterface{})interface{}{
returnnil
}
上下文包还具有几个常用功能:funcWithCancel(父上下文)(ctx上下文,取消CancelFunc)funcWithDeadline(父上下文,截止时间.Time)(上下文,CancelFunc)funcWithTimeout(父上下文,超时时间。持续时间)(上下文,CancelFunc)funcWithValue(父上下文,键,val接口{})上下文
请注意,这些方法意味着可以一次继承上下文以实现其他功能,例如,使用WithCancel函数传入根上下文,它会创建一个子上下文,该子上下文具有取消上下文的附加功能,然后使用此方法将context(context01)作为父上下文,并将其作为第一个参数传递给WithDeadline函数,与子context(context01)相比,获得子context(context02),它具有一个附加功能,可在之后自动取消上下文最后期限。
WithCancel
对于通道,尽管通道也可以通知许多嵌套的goroutine退出,但通道不是线程安全的,而上下文是线程安全的。
例如:
packagemain
import(
"runtime"
"fmt"
"time"
"context"
)
funcmonitor2(chchanbool,indexint){
for{
select{
casev:=<-ch:
fmt.Printf("monitor2:%v,thereceivedchannelvalueis:%v,ending\n",index,v)
return
default:
fmt.Printf("monitor2:%vinprogress...\n",index)
time.Sleep(2*time.Second)
}
}
}
funcmonitor1(chchanbool,indexint){
for{
gomonitor2(ch,index)
select{
casev:=<-ch:
//thisbranchisonlyreachedwhenthechchannelisclosed,orwhendataissent(eithertrueorfalse)
fmt.Printf("monitor1:%v,thereceivedchannelvalueis:%v,ending\n",index,v)
return
default:
fmt.Printf("monitor1:%vinprogress...\n",index)
time.Sleep(2*time.Second)
}
}
}
funcmain(){
varstopSingalchanbool=make(chanbool,0)
fori:=1;i<=5;i=i+1{
gomonitor1(stopSingal,i)
}
time.Sleep(1*time.Second)
//closeallgourtines
cancel()
//waiting10seconds,ifthescreendoesnotdisplay,allgoroutineshavebeenshutdown
time.Sleep(10*time.Second)
println(runtime.NumGoroutine())
println("mainprogramexit!!!!")
}
执行的结果是:
monitor1:5inprogress...
monitor2:5inprogress...
monitor1:2inprogress...
monitor2:2inprogress...
monitor2:1inprogress...
monitor1:1inprogress...
monitor1:4inprogress...
monitor1:3inprogress...
monitor2:4inprogress...
monitor2:3inprogress...
monitor1:4,thereceivedchannelvalueis:false,ending
monitor1:3,thereceivedchannelvalueis:false,ending
monitor2:2,thereceivedchannelvalueis:false,ending
monitor2:1,thereceivedchannelvalueis:false,ending
monitor1:1,thereceivedchannelvalueis:false,ending
monitor2:5,thereceivedchannelvalueis:false,ending
monitor2:3,thereceivedchannelvalueis:false,ending
monitor2:3,thereceivedchannelvalueis:false,ending
monitor2:4,thereceivedchannelvalueis:false,ending
monitor2:5,thereceivedchannelvalueis:false,ending
monitor2:1,thereceivedchannelvalueis:false,ending
monitor1:5,thereceivedchannelvalueis:false,ending
monitor1:2,thereceivedchannelvalueis:false,ending
monitor2:2,thereceivedchannelvalueis:false,ending
monitor2:4,thereceivedchannelvalueis:false,ending
1
mainprogramexit!!!!
这里使用一个通道向所有goroutine发送结束通知,但是这里的情况相对简单,如果在一个复杂的项目中,假设多个goroutine有某种错误并重复执行,则可以重复关闭或关闭该通道通道,然后向其写入值,从而触发运行时恐慌。这就是为什么我们使用上下文来避免这些问题的原因,以WithCancel为例:
packagemain
import(
"runtime"
"fmt"
"time"
"context"
)
funcmonitor2(ctxcontext.Context,numberint){
for{
select{
casev:=<-ctx.Done():
fmt.Printf("monitor:%v,thereceivedchannelvalueis:%v,ending\n",number,v)
return
default:
fmt.Printf("monitor:%vinprogress...\n",number)
time.Sleep(2*time.Second)
}
}
}
funcmonitor1(ctxcontext.Context,numberint){
for{
gomonitor2(ctx,number)
select{
casev:=<-ctx.Done():
//thisbranchisonlyreachedwhenthechchannelisclosed,orwhendataissent(eithertrueorfalse)
fmt.Printf("monitor:%v,thereceivedchannelvalueis:%v,ending\n",number,v)
return
default:
fmt.Printf("monitor:%vinprogress...\n",number)
time.Sleep(2*time.Second)
}
}
}
funcmain(){
varctxcontext.Context=nil
varcancelcontext.CancelFunc=nil
ctx,cancel=context.WithCancel(context.Background())
fori:=1;i<=5;i=i+1{
gomonitor1(ctx,i)
}
time.Sleep(1*time.Second)
//closeallgourtines
cancel()
//waiting10seconds,ifthescreendoesnotdisplay,allgoroutineshavebeenshutdown
time.Sleep(10*time.Second)
println(runtime.NumGoroutine())
println("mainprogramexit!!!!")
}
WithTimeout和WithDeadline
WithTimeout和WithDeadline在用法和功能上基本相同,它们都表示上下文将在一定时间后自动取消,唯一的区别可以从函数的定义中看出,传递给WithDeadline的第二个参数是类型time.Duration类型,它是一个相对时间,表示取消超时后的时间。例:
packagemain
import(
"runtime"
"fmt"
"time"
"context"
)
funcmonitor2(ctxcontext.Context,indexint){
for{
select{
casev:=<-ctx.Done():
fmt.Printf("monitor2:%v,thereceivedchannelvalueis:%v,ending\n",index,v)
return
default:
fmt.Printf("monitor2:%vinprogress...\n",index)
time.Sleep(2*time.Second)
}
}
}
funcmonitor1(ctxcontext.Context,indexint){
for{
gomonitor2(ctx,index)
select{
casev:=<-ctx.Done():
//thisbranchisonlyreachedwhenthechchannelisclosed,orwhendataissent(eithertrueorfalse)
fmt.Printf("monitor1:%v,thereceivedchannelvalueis:%v,ending\n",index,v)
return
default:
fmt.Printf("monitor1:%vinprogress...\n",index)
time.Sleep(2*time.Second)
}
}
}
funcmain(){
varctx01context.Context=nil
varctx02context.Context=nil
varcancelcontext.CancelFunc=nil
ctx01,cancel=context.WithCancel(context.Background())
ctx02,cancel=context.WithDeadline(ctx01,time.Now().Add(1*time.Second))//Ifit'sWithTimeout,justchangethislineto"ctx02,cancel=context.WithTimeout(ctx01,1*time.Second)"
defercancel()
fori:=1;i<=5;i=i+1{
gomonitor1(ctx02,i)
}
time.Sleep(5*time.Second)
ifctx02.Err()!=nil{
fmt.Println("thecauseofcancelis:",ctx02.Err())
}
println(runtime.NumGoroutine())
println("mainprogramexit!!!!")
}
WithValue
一些必需的元数据也可以通过上下文传递,该上下文将附加到上下文中以供使用。元数据作为键值传递,但请注意,键必须具有可比性,并且值必须是线程安全的。
packagemain
import(
"runtime"
"fmt"
"time"
"context"
)
funcmonitor(ctxcontext.Context,indexint){
for{
select{
case<-ctx.Done():
//thisbranchisonlyreachedwhenthechchannelisclosed,orwhendataissent(eithertrueorfalse)
fmt.Printf("monitor%v,endofmonitoring.\n",index)
return
default:
varvalueinterface{}=ctx.Value("Nets")
fmt.Printf("monitor%v,ismonitoring%v\n",index,value)
time.Sleep(2*time.Second)
}
}
}
funcmain(){
varctx01context.Context=nil
varctx02context.Context=nil
varcancelcontext.CancelFunc=nil
ctx01,cancel=context.WithCancel(context.Background())
ctx02,cancel=context.WithTimeout(ctx01,1*time.Second)
varctx03context.Context=context.WithValue(ctx02,"Nets","Champion")//key:"Nets",value:"Champion"
defercancel()
fori:=1;i<=5;i=i+1{
gomonitor(ctx03,i)
}
time.Sleep(5*time.Second)
ifctx02.Err()!=nil{
fmt.Println("thecauseofcancelis:",ctx02.Err())
}
println(runtime.NumGoroutine())
println("mainprogramexit!!!!")
}
关于上下文,还有一些注意事项:不要将Context存储在结构类型中,而是将Context明确传递给需要它的每个函数,并且Context应该是第一个参数。
即使函数允许,也不要传递nilContext,或者如果您不确定要使用哪个Context,请传递context。不要将可能作为函数参数传递给上下文值的变量传递。
到此这篇关于golang中context的作用的文章就介绍到这了,更多相关golang中context的作用内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。