当前位置: 首页 > 科技观察

Go语言语境优秀实践

时间:2023-03-16 15:41:11 科技观察

1.简介Go语言在v1.7中引入了context包。关于它的使用,我们在之前的文章中已经介绍过了,有兴趣的读者可以阅读。在本文中,我们介绍了context包的最佳实践,包括传值、超时和取消。2.值传递我们可以使用上下文包的funcWithValue()函数来传递数据。funcmain(){ctx:=context.WithValue(context.Background(),"ctxKey1","ctxVal")gofunc(ctxcontext.Context){//读取ctx的值数据,ok:=ctx.Value("ctxKey1").(string)ifok{fmt.Printf("subgoroutinegetvaluefromparentgoroutine,val=%s\n",data)}}(ctx)time.Sleep(1*time.Second)}输出结果:子协程从父协程获取值,val=ctxVal阅读上面的代码,我们使用funcWithValue()函数创建上下文,并传递key为ctxKey1的数据。我们知道context是并发安全的,所以每次使用context传递一个新的数据,都需要使用funcWithValue()函数来创建一个新的context,并把parentcontext包裹起来。传递多个数据...ctx:=context.WithValue(context.Background(),"ctxKey1","ctxVal")ctx=context.WithValue(ctx,"ctxKey2","ctxVal2")ctx=context.WithValue(ctx,"ctxKey3","ctxVal3")...阅读上面的代码,我们可以发现,如果使用context传递多个数据,则需要使用funcWithValue()来创建多个context。虽然我们的需求可以通过使用funcWithValue()创建多个context来实现,但是却让代码不再优雅,降低了性能。如何处理?对于这种场景,我们可以参考gRPC框架元数据包的代码。定义一个map,通过传递maptype的值,实现需要使用context传递多个数据的需求。funcmain(){ctxVal:=make(map[string]string)ctxVal["k1"]="v1"ctxVal["k2"]="v2"ctx:=context.WithValue(context.Background(),"ctxKey1",ctxVal)gofunc(ctxcontext.Context){//读取ctx的值数据,ok:=ctx.Value("ctxKey1").(map[string]string)ifok{fmt.Printf("subgoroutinegetvaluefromparentgoroutine,val=%+v\n",data)}}(ctx)time.Sleep(1*time.Second)}输出结果:subgoroutinegetvaluefromparentgoroutine,val=map[k1:v1k2:v2]修改传输数据使用context包的funcWithValue()函数传输数据。不建议在传输过程中修改数据。如果在传输过程中遇到需要修改数据的场景,我们可以采用COW的方式来避免数据竞争。funcmain(){ctxVal:=make(map[string]string)ctxVal["k1"]="v1"ctxVal["k2"]="v2"ctx:=context.WithValue(context.Background(),"ctxKey1",ctxVal)gofunc(ctxcontext.Context){//读取ctx的值数据,ok:=ctx.Value("ctxKey1").(map[string]string)ifok{ctxVal:=make(map[string]string)fork,v:=rangedata{ctxVal[k]=v}ctxVal["k3"]="v3"ctx=context.WithValue(ctx,"ctxKey1",ctxVal)数据,确定:=ctx.Value("ctxKey1").(map[string]string)if!ok{fmt.Printf("子goroutine从父goroutine获取值,val=%+v\n",nil)}fmt.Printf("subgoroutinegetvaluefromparentgoroutine,val=%+v\n",data)}}(ctx)time.Sleep(1*time.Second)}输出结果:subgoroutinegetvaluefromparentgoroutine,val=map[k1:v1k2:v2k3:v3]阅读上面的代码,我们通过COW(copyonwrite)修改context传过来的数据。3.超时我们可以使用context包的funcWithTimeout()函数来设置超时时间,避免请求阻塞。funcmain(){ctx,cancel:=context.WithTimeout(context.Background(),1*time.Millisecond)defercancel()select{case<-time.After(1*time.Second):fmt.Println("overslept")case<-ctx.Done():fmt.Println(ctx.Err())}outputresult:contextdeadlineexceeded阅读上面的代码,我们使用funcWithTimeout()函数来创建一个1ms的取消context,使用select...case...读取ctx.Done(),从而取消监听context的goroutine。4.取消我们可以使用context包的funcWithCancel()函数来取消操作,从而避免goroutine泄露。funcmain(){gen:=func()<-chanint{dst:=make(chanint)gofunc(){varnintfor{dst<-nn++}}()returndst}forn:=rangegen(){fmt.Println(n)ifn==5{break}}time.Sleep(1*time.Second)}outputresult:012345阅读上面的代码,我们创建一个gen()函数,开始一个goroutine生成一个整数,循环调用gen()函数输出生成的整数。当整数值为5时,停止循环。从输出结果来看,没有发现问题。但是,这段代码实际上会导致goroutine泄漏,因为gen()函数一直在无限循环。如何处理?我们可以使用funcWithCancel()函数创建一个context作为gen()函数的第一个参数,当循环停止时,调用contextCancelFunc取消由gen()函数启动的goroutine。funcmain(){gen:=func(ctxcontext.Context)<-chanint{dst:=make(chanint)gofunc(){varnintfor{dst<-nn++}}()returndst}ctx,cancel:=context.WithCancel(context.Background())defercancel()forn:=rangegen(ctx){fmt.Println(n)ifn==5{cancel()break}}time.Sleep复制代码(1*time.Second)}输出结果:0123455作为本文的总结,我们介绍context包的使用,timeout和cancellation。context包的这三个函数不仅可以用于跨goroutine的操作,而且还可以用于跨服务的操作。