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
1秒
1秒
1秒
退出协程

看注释和执行流程,基本能理解,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之间传值。

posted @ 2024-04-18 16:53  飞翔码农  阅读(225)  评论(0编辑  收藏  举报