context: goroutine 上下文

文章目录

      • Context 接口
      • context 包中包含如下几种衍生的子 Context
        • 使用 context.WithCancel 控制子 groutine 结束
        • 通过 context.WithValue 来传值
        • 超时取消 context.WithTimeout
        • 截止时间 context.WithTimeout 和 超时时间类似
        • 控制多层 goroutine 结束

Context 接口

type Context interface {
    // 最后期限  返回期限时间 ok
    Deadline() (deadline time.Time, ok bool)
    // 返回一个channel。当times out或者调用cancel方法时,将会close掉。
    Done() <-chan struct{}
    // Context 被取消的原因 比如 timeout 、canceled
    Err() error
    // 
    Value(key interface{}) interface{}
}

context 包中包含如下几种衍生的子 Context

// 返回一个 可取消 Context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// 返回一个 在给定期限到期是 取消的 Context
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
// 返回一个 在 超时 时取消的 Context
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
// 该方法返回的 Context 与 “取消” 无关,它返回一个绑定了一个键值对数据的Context,用来传递上下文数据
func WithValue(parent Context, key, val interface{}) Context

使用 context.WithCancel 控制子 groutine 结束

一个网络请求 Request,每个 Request 都需要开启一个goroutine 做一些事情,这些 goroutine又可能会开启其他的 goroutine。所以我们需要一种可以跟踪 goroutine 的方案,才可以达到控制他们的目的,这就是 Go 语言为我们提供的Context,称之为上下文非常贴切,它就是 goroutine的上下文。

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go func(ctx context.Context){
        for {
            select {
            case <-ctx.Done():
                fmt.Println("主 goroutine 结束")
                return 
            default:
                fmt.Println("监控 goroutine 中...")
                time.Sleep(time.Second)
            }
        }
    }(ctx)

    fmt.Println("主进程运行中。")
    time.Sleep(3 * time.Second)
    fmt.Println("主进程任务处理完成,停止监控。")
    cancel()
    time.Sleep(3 * time.Second)
}

context.Background() 返回一个空的 Context(emptyCtx),这个空的 Context 一般用于整个 Context 树的根节点。然后我们使用 context.WithCancel(parent) 函数,创建一个可取消的子 Context(继承自context.Background()),然后当作参数传给 goroutine 使用,这样就可以使用这个子 Context 跟踪这个 goroutine

goroutine 中,使用 select 调用 <-ctx.Done() 判断是否要结束,如果ctx.Done() 返回一个通道,该通道若关闭,就可以返回结束 goroutine 了;如果没有关闭,就会继续执行 default 分支。

那么是如何发送结束指令的呢?这就是示例中的 cancel 函数啦,它是我们调用context.WithCancel(parent) 函数生成子 Context 的时候返回的,第二个返回值就是这个取消函数,它是CancelFunc类型的。我们调用它就会关闭 ctx.done(done 属性是一个无缓冲通道),然后我们的监控 goroutine 中的 selecrt 就会收到,返回结束。

通过 context.WithValue 来传值

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    valueCtx := context.WithValue(ctx, "key", "add value")  

    go watch(valueCtx)
    time.Sleep(10 * time.Second)
    cancel()

    time.Sleep(5 * time.Second)
}

func watch(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            //get value
            fmt.Println(ctx.Value("key"), "is cancel")

            return
        default:
            //get value
            fmt.Println(ctx.Value("key"), "int goroutine")

            time.Sleep(2 * time.Second)
        }
    }
}

超时取消 context.WithTimeout

package main

import (
    "fmt"
    "time"

    "context"
)


func work(ctx context.Context) error {


    for i := 0; i < 1000; i++ {
        select {
        case <-time.After(1 * time.Second):
            fmt.Println("Doing some work ", i)

        // we received the signal of cancelation in this channel
        case <-ctx.Done():
            fmt.Println("Cancel the context ", i)
            return ctx.Err()
        }
    }
    return nil
}

func main() {
    // 设置超时时间 4s
    ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
    defer cancel()

    fmt.Println("Hey, I'm going to do some work")
    go work(ctx)

    time.Sleep(6 * time.Second)
    fmt.Println("Finished. I'm going home")
}

截止时间 context.WithTimeout 和 超时时间类似

控制多层 goroutine 结束

package main

import (
    "context"
    "fmt"
    "time"
)

func main(){
    t := time.Now().Add(5 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), t)
    defer cancel()

    fmt.Println("Start ...")
    go parent(ctx)

    select {
    case <- time.After(6 * time.Second):
        fmt.Println("End ...")
    }
}

func parent(ctx context.Context) {
    fmt.Println("\t[Goroutine 1] Goroutine 1 start...")
    i := 0
    for {
        select {
        case <- ctx.Done():
            fmt.Println("\t[Goroutine 1] Goroutine 1 end ... \n")
            return
        default:
            i++
            fmt.Printf("\t[Goroutine 1] create a child processes -- %d\n", i)
            vctx := context.WithValue(ctx, "id", i)   // vctx 连接自 ctx, 当 ctx 被取消时 vctx 也会被取消 
            go child(vctx) 
            time.Sleep(time.Second)
        }
    }
}

func child(ctx context.Context) {
    fmt.Printf("\t\t[Child Goroutine %d] Goroutine start...\n", ctx.Value("id"))
    for {
        select {
        case <- ctx.Done():
            fmt.Printf("\t\t[Child Goroutine %d] Goroutine end ...\n", ctx.Value("id"))
            return
        }
    }
}

/ * 输出:
Start ...
        [Goroutine 1] Goroutine 1 start...
        [Goroutine 1] create a child processes -- 1
                [Child Goroutine 1] Goroutine start...
        [Goroutine 1] create a child processes -- 2
                [Child Goroutine 2] Goroutine start...
        [Goroutine 1] create a child processes -- 3
                [Child Goroutine 3] Goroutine start...
        [Goroutine 1] create a child processes -- 4
                [Child Goroutine 4] Goroutine start...
        [Goroutine 1] create a child processes -- 5
                [Child Goroutine 5] Goroutine start...
                [Child Goroutine 5] Goroutine end ...
                [Child Goroutine 1] Goroutine end ...
                [Child Goroutine 2] Goroutine end ...
                [Child Goroutine 3] Goroutine end ...
                [Child Goroutine 4] Goroutine end ...
        [Goroutine 1] Goroutine 1 end ... 
End ...
*/

你可能感兴趣的:(go语言)