Golang context初探

作者: 天唯 | 来源:发表于2016-12-25 23:28 被阅读2948次

    什么是context

    从go1.7开始,golang.org/x/net/context包正式作为context包进入了标准库。那么,这个包到底是做什么的呢?根据官方的文档说明:

    Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.

    也就是说,通过context,我们可以方便地对同一个请求所产生地goroutine进行约束管理,可以设定超时、deadline,甚至是取消这个请求相关的所有goroutine。形象地说,假如一个请求过来,需要A去做事情,而A让B去做一些事情,B让C去做一些事情,A、B、C是三个有关联的goroutine,那么问题来了:假如在A、B、C还在处理事情的时候请求被取消了,那么该如何优雅地同时关闭goroutine A、B、C呢?这个时候就轮到context包上场了。

    如何使用context

    在开始说明如何使用context之前,先来看看context有什么相关的方法定义。
    先来看看context的代码示例:

    package main
    
    import (
        "context"
        "log"
        "net/http"
        _ "net/http/pprof"
        "time"
    )
    
    func main() {
        go http.ListenAndServe(":8989", nil)
        ctx, cancel := context.WithCancel(context.Background())
        go func() {
            time.Sleep(3 * time.Second)
            cancel()
        }()
        log.Println(A(ctx))
        select {}
    }
    
    func C(ctx context.Context) string {
        select {
        case <-ctx.Done():
            return "C Done"
        }
        return ""
    }
    
    func B(ctx context.Context) string {
        ctx, _ = context.WithCancel(ctx)
        go log.Println(C(ctx))
        select {
        case <-ctx.Done():
            return "B Done"
        }
        return ""
    }
    
    func A(ctx context.Context) string {
        go log.Println(B(ctx))
        select {
        case <-ctx.Done():
            return "A Done"
        }
        return ""
    }
    

    运行结果:

    2016/12/25 22:27:00 A Done
    2016/12/25 22:27:00 C Done
    2016/12/25 22:27:00 B Done

    pprof截图,刚开始的时候:

    刚开始.png

    A、B、C结束后:

    结束后.png

    在这里,我们用http的pprof来查看运行时有多少个goroutine正在执行,
    go http.ListenAndServe(":8989", nil)
    这一句是启动一个http服务器,用来查看程序的一些运行信息。
    我们用context.Background()来实例化一个context,然后调用WithCancel()方法来返回一个context和一个取消的方法,并在3秒后调用这个cancel方法关闭goroutine A、B、C。从程序的运行结果看,我们调用了一次cancel方法,子goroutine的ctx.Done()都收到了关闭信号。从pprof的截图也可以看到,从一开始有5个goroutine,到关闭后剩下两个,也就是A、B、C三个goroutine都已经关闭了。
    这里的例子是直接调用了context.WithCancel(),我们也可以使用context.WithTimeout()context.WithDeadline()来设置goroutine的超时时间和最终的运行时间。具体的用法可以看一下官方文档,这里就不细说了。另外有一个方法在例子中没有用到,那就是context.WithValue()。这个方法是用来传递在这次的请求处理中相关goroutine的共享变量,这与全局变量是有所区别的,因为它只在这次的请求范围内有效。

    context的使用规范

    在最新的1.8 beta版本中,很多相关的包都加入了context,比如database包。那么,在使用context的时候有哪些需要注意呢?

    • 不要把context存储在结构体中,而是要显式地进行传递
    • 把context作为第一个参数,并且一般都把变量命名为ctx
    • 就算是程序允许,也不要传入一个nil的context,如果不知道是否要用context的话,用context.TODO()来替代
    • context.WithValue()只用来传递请求范围的值,不要用它来传递可选参数
    • 就算是被多个不同的goroutine使用,context也是安全的

    context的初步了解暂时就先说这么多了,以后有空可以研究一下源码看看context是如何实现的。

    圣诞快乐!

    参考文章:
    Golang之Context的使用
    Golang context

    相关文章

      网友评论

      • gBvqoG:log.Println(Add(ctx))
        应为
        log.Println(A(ctx))

      本文标题:Golang context初探

      本文链接:https://www.haomeiwen.com/subject/ziydvttx.html