Go Context

Context 介绍

用于在Goroutine之间传递请求作用域数据、取消信号和超时时间。从而实现更加灵活可靠的并发编程。如果上层Goroutine失败后,可以通过信号的同步取消下层的其余操作,避免对计算资源的浪费。

Context 类型

根Context

  1. context.Background()

    返回非 nil 的空context值,是初始化 context 的默认值,还不具备 context 的其他功能,用作传入请求的顶级上下文,所有其他的上下文都应该从它衍生出来。

  2. context.TODO()

    同 background ,底层源码皆是通过 new(emptyCtx) 语句初始化,初始化过程中接口均实现的是空方法,并不具备其他功能。区别是TODO应该仅在不确定应该使用哪种上下文时使用。

衍生Context

  1. WithCancel(parent Context) (ctx Context, cancel CancelFunc)

    接受一个父context.Context作为参数,并返回一个新的context.Context和一个CancelFunc函数。新的上下文会继承父上下文的属性,但也可以通过调用CancelFunc来主动取消上下文。当调用CancelFunc时,与该上下文相关的所有子上下文也会被取消。

    package main
    
    import (
        "context"
        "fmt"
    )
    
    func main() {
        gen := func(ctx context.Context) <-chan int &#123;
            dst := make(chan int)
            n := 1
            go func() &#123;
                for &#123;
                    select &#123;
                    case <-ctx.Done():
                        return
                    case dst <- n:
                        n++
                    &#125;
                &#125;
            &#125;()
            return dst
        &#125;
    
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        // cancel 是幂等函数,只有第一次调用会触发取消操作,后续的调用不会产生任何额外的效果。
    
        for n := range gen(ctx) &#123;
            fmt.Println(n)
            if n == 5 &#123;
                break
            &#125;
        &#125;
    &#125;
    
  1. WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

    接受一个父context.Context和一个截止时间d作为参数,并返回一个新的context.Context。这个新的上下文会在指定的截止时间之前被取消。当截止时间到达时,与此上下文相关的Done通道将被关闭。

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    const shortDuration = 1 * time.Millisecond
    
    func main() &#123;
        d := time.Now().Add(shortDuration)
        ctx, cancel := context.WithDeadline(context.Background(), d)
    
        defer cancel()
    
        select &#123;
        case <-time.After(1 * time.Second):
            fmt.Println("overslept")
        case <-ctx.Done():
            fmt.Println(ctx.Err())
        &#125;
    
    &#125;
    
    // 在WithCancel的基础上,增加了deadline,是一个时间值。当时间超过了截止日期后会调用 context.timerCtx.cancel 同步取消信号。
  1. WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

    接受一个父context.Context和一个超时时间timeout作为参数,并返回一个新的context.Context。这个新的上下文会在指定的超时时间过去之后被取消。超时时间可以是一个持续时间间隔(time.Duration)。

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    const shortDuration = 1 * time.Millisecond
    
    func main() &#123;
        ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
        defer cancel()
    
        select &#123;
        case <-time.After(1 * time.Second):
            fmt.Println("overslept")
        case <-ctx.Done():
            fmt.Println(ctx.Err()) // prints "context deadline exceeded"
        &#125;
    &#125;
    
    // 与WithDeadline类似,只不过传入的是duration,而不是具体的时间值。底层仍然调用的是WithDeadline
    // 并通过time.Now().Add(timeout)将duration转为具体的时间值
  1. WithValue(parent Context, key, val any) Context

    接受一个父context.Context、一个键key和一个值val作为参数,并返回一个新的context.Context。新的上下文会继承父上下文的属性,并将键值对存储在上下文中。这样,在整个上下文树中,可以使用context.Value方法根据键访问对应的值。

    package main
    
    import (
        "context"
        "fmt"
    )
    
    func main() &#123;
        type favContextKey string
    
        f := func(ctx context.Context, k favContextKey) &#123;
            if v := ctx.Value(k); v != nil &#123;
                fmt.Println("found value:", v)
                return
            &#125;
            fmt.Println("key not found:", k)
        &#125;
    
        k := favContextKey("language")
        ctx := context.WithValue(context.Background(), k, "Go")
    
        f(ctx, k)
        f(ctx, favContextKey("color"))
    
    &#125;
    

使用建议

  • 同一个 context 可以传递到多个 goroutine 中,并且,context 是并发安全的。
  • 关键参数不应传入Context中,而应该显式的声明在函数中,从而提高代码的可读性。
  • context一般作为函数的第一个参数传入,并命名为ctx
  • 及时取消Context。当不再需要上下文时,调用其cancel函数以避免资源泄漏。