本文正在参加「金石方案 . 瓜分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 创立并存储对应的上下文信息。

下面是运转结果:

从使用角度浅谈 Go 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程序员面试笔试宝典 – 机械工业出版社