• golang开发 深入理解 context


    context的历史

    context包在Go 1.7版本正式加入Go标准库。在加入之前我们看看Go团队核心成员Sameer Ajmani在2014年发表的一篇关于context介绍博客,地址:https://go.dev/blog/context 下面是介绍的翻译。

    在Go服务器中,每个传入的请求都在自己的goroutine中处理。请求处理程序经常启动额外的goroutine来访问后端,如数据库和RPC服务。处理请求的goroutine集合通常需要访问特定于请求的值,例如最终用户的身份、授权令牌和请求的截止日期。当一个请求被取消或超时时,所有处理该请求的goroutine都应该迅速退出,这样系统就可以回收它们正在使用的任何资源。

    在Google,我们开发了一个 context 包,可以轻松地将请求范围内的值、取消信号和截止日期跨API边界传递给处理请求所涉及的所有goroutine。该软件包作为上下文公开提供。

    可以看到介绍里面主要说的是,应用中怎么控制大量的goroutine退出释放资源、请求范围内怎么传值,也就是说,context的引入主要是为了解决这两个问题。

    context的基本使用

    context初始化的方法有五个,一个是main goroutine 初始化,一个主要是是用来传值的,经常使用的就三个。
    在这里插入图片描述
    未超时的时候,执行取消,依赖取消函数

    func main() {
        ctx := context.Background()
        ctx = context.WithValue(ctx, "main", "main-value")
    
        ctx1,cancel := context.WithDeadline(ctx, time.Now().Add(3*time.Second))
        go cancelFunc(ctx1)
        //取消函数
        cancel()
        time.Sleep(time.Duration(5*time.Second))
    }
    
    执行结果
    main-param main-value
    退出协程
    

    超时的时候执行结果,超过三秒的超时时间,依赖超时时间

    func main() {
        ctx := context.Background()
        ctx = context.WithValue(ctx, "main", "main-value")
    
        ctx1,_ := context.WithDeadline(ctx, time.Now().Add(3*time.Second))
        go cancelFunc(ctx1)
        time.Sleep(time.Duration(5*time.Second))
    }
    
    执行结果
    main-param main-value
    111秒
    退出协程
    

    看注释和执行流程,基本能理解,main函数调用cancel就能取消正在执行的goroutine协程。WithDeadline和WithTimeout 除过可以通过调用取消函数,还可以通过设置超时时间点和时间段取消goroutine的执行。

    context如何控制多个goroutine,如何传值给多个goroutine

    在这里插入图片描述
    看图示,root context控制着所有的goroutine,context1控制着 goroutine2 goroutine3 goroutine4 goroutine5 goroutine6 goroutine7,context2 控制着goroutine4 goroutine5,context3 控制着goroutine6 goroutine7。

    使用图示的goroutine 编写测试代码

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func func1(ctx context.Context) {
        fmt.Println("func1-main-param", ctx.Value("main"))
        ctx1 := context.WithValue(ctx, "func1-param", "func1-value")
        go func2(ctx1)
        select {
        case <-ctx1.Done():
            fmt.Println("goroutine 1 exit")
        }
    }
    
    func func2(ctx context.Context) {
        fmt.Println("func2-main-param", ctx.Value("main"))
        fmt.Println("func2-func1-param", ctx.Value("func1-param"))
        ctx2, _ := context.WithCancel(ctx)
        go func4(ctx2)
        go func5(ctx2)
        select {
        case <-ctx2.Done():
            fmt.Println("goroutine 2 exit")
        }
    }
    
    func func4(ctx context.Context) {
        select {
        case <-ctx.Done():
            fmt.Println("goroutine 4 exit")
        }
    }
    
    func func5(ctx context.Context) {
        select {
        case <-ctx.Done():
            fmt.Println("goroutine 5 exit")
        }
    }
    
    func main() {
        ctx := context.Background()
        ctx = context.WithValue(ctx, "main", "main-value")
        ctx,cancel := context.WithCancel(ctx)
        go func1(ctx)
        cancel()
        time.Sleep(time.Duration(5*time.Second))
    }
    
    

    运行结果

    func1-main-param main-value
    goroutine 1 exit
    func2-main-param main-value
    func2-func1-param func1-value
    goroutine 2 exit
    goroutine 5 exit
    goroutine 4 exit
    

    可以看到最终实现结果和场景,跟Sameer Ajmani在博客描述的是一样的,context的出现主要两个作用,控制 goroutine,goroutine之间传值。

  • 相关阅读:
    17 - 并发容器的使用:识别不同场景下最优容器
    联邦学习中的优化算法
    上手之Python之异常
    tp6 小程序码生成(带参数)
    带你认识什么是redis主从复制集群的原理和部署
    C#.Net筑基-运算符Family
    05_TCP并发服务器
    浏览器内核小知识
    高德地图实时定位接口的调用
    C++(day5)
  • 原文地址:https://www.cnblogs.com/feixiangmanon/p/18137318