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时,与该上下文相关的所有子上下文也会被取消。

     1package main
     2
     3import (
     4	"context"
     5	"fmt"
     6)
     7
     8func main() {
     9	gen := func(ctx context.Context) <-chan int {
    10		dst := make(chan int)
    11		n := 1
    12		go func() {
    13			for {
    14				select {
    15				case <-ctx.Done():
    16					return
    17				case dst <- n:
    18					n++
    19				}
    20			}
    21		}()
    22		return dst
    23	}
    24
    25	ctx, cancel := context.WithCancel(context.Background())
    26	defer cancel()
    27	// cancel 是幂等函数,只有第一次调用会触发取消操作,后续的调用不会产生任何额外的效果。
    28
    29	for n := range gen(ctx) {
    30		fmt.Println(n)
    31		if n == 5 {
    32			break
    33		}
    34	}
    35}
    
  2. WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

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

     1package main
     2
     3import (
     4	"context"
     5	"fmt"
     6	"time"
     7)
     8
     9const shortDuration = 1 * time.Millisecond
    10
    11func main() {
    12	d := time.Now().Add(shortDuration)
    13	ctx, cancel := context.WithDeadline(context.Background(), d)
    14
    15	defer cancel()
    16
    17	select {
    18	case <-time.After(1 * time.Second):
    19		fmt.Println("overslept")
    20	case <-ctx.Done():
    21		fmt.Println(ctx.Err())
    22	}
    23
    24}
    25
    26// 在WithCancel的基础上,增加了deadline,是一个时间值。当时间超过了截止日期后会调用 context.timerCtx.cancel 同步取消信号。
    
  3. WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

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

     1package main
     2
     3import (
     4	"context"
     5	"fmt"
     6	"time"
     7)
     8
     9const shortDuration = 1 * time.Millisecond
    10
    11func main() {
    12	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
    13	defer cancel()
    14
    15	select {
    16	case <-time.After(1 * time.Second):
    17		fmt.Println("overslept")
    18	case <-ctx.Done():
    19		fmt.Println(ctx.Err()) // prints "context deadline exceeded"
    20	}
    21}
    22
    23// 与WithDeadline类似,只不过传入的是duration,而不是具体的时间值。底层仍然调用的是WithDeadline
    24// 并通过time.Now().Add(timeout)将duration转为具体的时间值
    
  4. WithValue(parent Context, key, val any) Context

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

     1package main
     2
     3import (
     4	"context"
     5	"fmt"
     6)
     7
     8func main() {
     9	type favContextKey string
    10
    11	f := func(ctx context.Context, k favContextKey) {
    12		if v := ctx.Value(k); v != nil {
    13			fmt.Println("found value:", v)
    14			return
    15		}
    16		fmt.Println("key not found:", k)
    17	}
    18
    19	k := favContextKey("language")
    20	ctx := context.WithValue(context.Background(), k, "Go")
    21
    22	f(ctx, k)
    23	f(ctx, favContextKey("color"))
    24
    25}
    

使用建议

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