goco语言的上下文管理的使用_数据库
本文主要介绍了Go语言context上下文管理的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
context 有什么作用
Context主要用来传递goroutine之间的上下文信息,包括:取消信号、超时、截止时间、k-v等。
Go常用来写后台服务。构建一个http服务器通常只需要几行代码。
在Go的服务器中,通常每个请求都会启动几个goroutine同时工作:有的去数据库获取数据,有的调用下游接口获取相关数据helliphellip
这些goroutine需要共享这个请求的基本数据,比如登录令牌,处理请求的最大超时(如果超过这个值后返回数据,请求者会因为超时而收不到)等等。当请求被取消或处理时间过长时,可能是用户关闭了浏览器或超过了请求者指定的超时,请求者直接放弃请求结果。此时,所有为此请求工作的goroutine都需要迅速退出,因为他们的ldquo成绩rdquo不再需要了。在所有相关的goroutine退出后,系统可以回收相关的资源。
在围棋中,我们不能直接杀死协同学。协同的关闭通常由通道+选择控制。但是,在一些场景中,比如处理一个请求,会生成很多协程,这些协程是相互关联的:它们需要共享一些全局变量,有一个共同的截止时间,可以同时关闭。再用channel+select会比较麻烦,然后可以通过上下文实现。
一句话:context用于解决goroutine之间的退出通知和元数据传输的功能。
上下文使用起来非常方便。源代码提供了创建根节点上下文的函数:
func Background() Context
后台是空的上下文,不能取消,没有值,没有超时。对于根节点上下文,提供了四个函数来创建子节点上下文:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)func WithValue(parent Context, key, val interface{}) Context
上下文在函数传递之间传递。您只需要调用cancel函数向goroutines发送一个取消信号,或者调用Value函数在适当的时候获取上下文中的值。
不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为ctx。不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。
传递共享的数据
对于Web服务器开发,往往希望将处理一个请求的整个过程链接在一起,这非常依赖于Thread Local这个变量(可以理解为对于Go的单个协程是唯一的)。而Go语言中没有这个概念,调用函数时需要传递上下文。
package mainimport ( "context" "fmt")func main() { ctx := context.Background() process(ctx) ctx = context.WithValue(ctx, "traceId", "qcrao-2019") process(ctx)}func process(ctx context.Context) { traceId, ok := ctx.Value("traceId").(string) if ok { fmt.Printf("process over. trace_id=%sn", traceId) } else { fmt.Printf("process over. no trace_idn") }}
运行结果:
过程结束。没有trace _ id
进程结束。trace_id=qcrao-2019
第一次调用process函数的时候,ctx是空的一个上下文,那么自然,我们就无法得到traceId。第二次是通过WithValue函数创建一个上下文,分配traceId键,这样传入的Value值就可以自然取出来了。
取消 goroutine
我们先想象一个场景:打开外卖订单页面,外卖小哥的位置显示在地图上,每秒更新一次。app向后台发起websocket连接(现实中可能是轮询)请求后,后台启动一个协作进程,每秒计算一次弟弟的位置,并发送给端。如果用户退出此页面,后台需要ldquo取消rdquo在这个过程中,goroutine被退出,系统回收资源。
func Perform() { for { calculatePos() sendResult() time.Sleep(time.Second) }}
如果ldquo需要实现;取消rdquo函数,在不知道上下文的函数的情况下,你可能会这样做:在函数中加入一个指针型的bool变量,判断bool变量在for语句的开头是否由true变为false,如果变化则退出循环。
上面给出的简单方法可以达到想要的效果,没有任何问题,但是并不优雅,而且一旦协同学数量增加,嵌套起来会很麻烦。优雅的方法,很自然地使用上下文。
func Perform(ctx context.Context) { for { calculatePos() sendResult() select { case lt;-ctx.Done(): // 被取消,直接返回 return case lt;-time.After(time.Second): // block 1 秒钟 } }}
主要流程可能如下所示:
ctx, cancel := context.WithTimeout(context.Background(), time.Hour)go Perform(ctx)// ……// app 端返回页面,调用cancel 函数cancel()
注意一个细节,WithTimeout函数返回的上下文是和cancelFun分开的。上下文本身不会取消该功能。这样做的原因是取消函数只能由外部函数调用,这就阻止了子节点上下文调用取消函数,从而严格控制了信息流:从父节点上下文到子节点上下文。
防止 goroutine 泄漏
在前面的例子中,goroutine仍然会完成执行并最终返回,这可能会浪费更多的系统资源。下面是对ldquo的改编;如果不使用context来取消,goroutine就会泄漏。例子rdquo
func gen() lt;-chan int { ch := make(chan int) go func() { var n int for { ch lt;- n n++ time.Sleep(time.Second) } }() return ch}
这是一个可以生成无限整数的协程,但是如果我只需要它生成的前5个数,那么就会发生goroutine泄漏:
func main() { for n := range gen() { fmt.Println(n) if n == 5 { break } } // ……}
当n == 5时,直接破。然后gen函数的协程将执行一个无限循环,永不停止。有一个goroutine泄漏。
用上下文改进这个例子:
func gen(ctx context.Context) lt;-chan int { ch := make(chan int) go func() { var n int for { select { case lt;-ctx.Done(): return case ch lt;- n: n++ time.Sleep(time.Second) } } }() return ch}func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 避免其他地方忘记 cancel,且重复调用不影响 for n := range gen(ctx) { fmt.Println(n) if n == 5 { cancel() break } } // ……}
添加一个上下文,在中断前调用cancel函数,取消goroutine。收到取消信号后,gen函数直接退出,系统回收资源。
context.Value 的查找过程是怎样的
有点像链表,只是方向相反:上下文指向它的父节点,而链表指向下一个节点。使用Value函数,您可以创建valueCtx层来存储可以在goroutine之间共享的变量。
搜索时,会找到最后挂载的上下文节点,即相对较近的父节点的上下文。
这就是这篇关于在Go语言中使用上下文管理的文章。更多Go语言中上下文管理的相关内容,请搜索SourceSearch.com之前的文章或者继续浏览下面的相关文章。我希望你以后能更多地支持SourceSearch.com!
本站所有文章和图片均来自用户分享和网络收集,文章和图片版权归原作者及原出处所有,仅供学习与参考,请勿用于商业用途,如果损害了您的权利,请联系网站客服处理。