本文正在参加「金石方案 . 瓜分6万现金大奖」
从运用视点浅谈 Go context
〇、前语
这是《让咱们一同Golang》专栏的第47篇文章,本文从运用视点浅谈Go的context包,不过由于笔者水平缓工作经验限制,或许文章存在许多不足或过错,烦请指出斧正!
本专栏的其他文章:
- 深入理解 Golang map 设计理念与完成原理 –
- 云青青兮欲雨——Go的数组与切片傻傻分不清楚? –
- 安得倚天抽宝剑——Go中new到底在堆仍是栈中分配 –
- 从源码分析视点浅谈Golang Channel –
- …
一、context的效果是什么?
context译为“上下文”,主要在协程Goroutine之间传递上下文信息,包含撤销信号、超时时刻、截止时刻、键值对等等。
比方咱们用Go语言建立一个HTTP server,假如有一个恳求发送到server,它就会发动一个或多个Goroutine去工作,假如呼应恳求时调用的某个服务呼应速度很慢,就会导致恳求这个服务的Goroutine越来越多,导致内存占用暴涨,Go调度器和垃圾收回器压力很大,就会导致发送到server的恳求得不到呼应。
而context包的效果便是处理这个问题。
在Go里边,没有context包前,咱们一般运用channel和select来控制协程的关闭,可是当多个协程之间相互关联,有同享的数据时,运用channel和select就会比较费事,此刻咱们就需要用到context包。
二、context的运用:传递同享数据
package main
import (
"context"
"fmt"
)
func main() {
//上下文默认值,一切其他的上下文都从他衍生。一般用于main函数、初始化、测验或许尖端上下文
ctx := context.Background()
s, ok := ctx.Value("name").(string)
if !ok {
fmt.Println("nil")
} else {
fmt.Println(s)
}
//根据某个 context 创立并存储对应的上下文信息。
ctx = context.WithValue(ctx, "name", "ReganYue")
s, _ = ctx.Value("name").(string)
fmt.Println(s)
}
context.Background()是上下文默认值,一切其他的上下文都从他衍生。一般用于main函数、初始化、测验或许尖端上下文。context.WithValue()是根据某个 context 创立并存储对应的上下文信息。
下面是运转结果:
第一次取上下文中的“name”时,由于ctx是一个空的context,因而取不出来,ok为false,因而咱们输出的是nil,后面运用context.WithValue()创立并存储了上下文信息,因而第二次取时,能够取到“name”中的数据ReganYue
。
二、context的运用:守时撤销
当某个服务由于业务负载过重、网络推迟高的状况导致恳求堵塞时,需要用到context包中的WithTimeout(根据父级 context,创立一个具有超时时刻(Timeout)的新 context)。
package main
import (
"context"
"fmt"
"time"
)
func main() {
timeout, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second)
defer cancelFunc()
select {
case <-time.After(2 * time.Second):
fmt.Println("Hello")
case <-timeout.Done():
fmt.Println("finish finish")
}
}
假如是这样的话,会输出:
finish finish
由于已经context.WithTimeout设置的守时是1秒钟,1秒钟过后,timeout就会Done了,所以输出finish finish,然后再调用cancelFunc()。假如咱们将context.WithTimeout设置的守时大于2秒钟,比方5秒钟,就会输出:
Hello
三、context的运用:防止Goroutine走漏
上述运用事例中,若不运用context,Goroutine仍然会履行结束,可是某些场景下,若不用context撤销,会导致goroutine走漏。
看下面这个例子:
package main
import (
"fmt"
"time"
)
func gen() <-chan int {
ch := make(chan int)
go func() {
var n int
for {
ch <- n
n++
time.Sleep(1 * time.Second)
}
}()
return ch
}
func main() {
for n := range gen() {
fmt.Println(n)
if n == 5 {
break
}
}
fmt.Println("....")
}
在该例子中,当取整数5后,咱们直接break,这时候往管道ch发送数字的协程Goroutine就会被堵塞,咱们常称之为Goroutine走漏。
如何用context处理这个问题呢?咱们看看下面:
package main
import (
"context"
"fmt"
"time"
)
func gen(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
var n int
for {
select {
case ch <- n:
n++
time.Sleep(1 * time.Second)
case <-ctx.Done():
return
}
}
}()
return ch
}
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
cancelFunc()
break
}
}
fmt.Println("....")
}
在主协程中调用break前,咱们调用cancelFunc,让履行gen函数的协程履行return,让GC收回资源。
参考文献:
深度解密Go语言之context – 知乎 zhuanlan.zhihu.com/p/68792989
Context should go away for Go 2 — faiface blog faiface.github.io/post/contex…
Go程序员面试笔试宝典 – 机械工业出版社