context学习笔记|实践记录以及工具使用

64 阅读3分钟

在实际应用场景中常常有对协程跟踪的需求,比如我们的业务代码中会启动大量goroutine,而在实际中常常出现调用请求终止,因此开启的大量goroutine我们需要关闭这些goroutine来保证我们的计算机没有因此浪费内存和计算资源;Golang语言提供了context包,其主要用于控制goroutine的生命周期和在不同的goroutine间传递信息,传递的信息可以为请求范围的值、截止日期、取消信号,我们可以通过context跟踪开启的goroutine,并向它们下达终止命令。

context接口

首先我们来看golang中对context的定义,如下所示;context在golang中被定义为接口,接口包含了如下四种方法;首先是Done()方法,其返回类型为一个时间和布尔类型,布尔类型返回值表明是否设置了截止时间,时间为设置的截止时间;其次是Done()方法,其返回类型为struct类型的只读chan,多次调用Done()方法返回的是同一个chan,这个chan有值可读的时候说明收到了终止信号,协程因此可以做退出处理;Err()方法会返回一个error说明为什么取消context,当Done得到的chan被关闭时才会返回非空值;Value(key any)该context绑定的一个值。

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}

context创建与使用

func main() {
    var wg sync.WaitGroup = sync.WaitGroup{}
    wg.Add(1)
    var ctx context.Context = context.Background() //创建一个空context
    ctx1, ok := context.WithTimeout(ctx, 2*time.Second)//创建一个定时可取消context
    defer ok()
    go func() {
       defer wg.Done()
       var ctx11 context.Context = context.WithoutCancel(ctx1)//创建一个可取消context
       go func() {
          for {
             select {
             case <-ctx11.Done():
                fmt.Println("groutine1-1 is cancel", time.Now())
                return
             default:
                fmt.Println("goroutine1-1 is running", time.Now())
             }
          }
       }()
       for {
          select {
          case <-ctx1.Done()://收到退出信号 退出
             fmt.Println("groutine1 is cancel", time.Now())
             return
          default:
             fmt.Println("goroutine1 is running", time.Now())
          }
       }
    }()
    wg.Wait()
}

空context

context在golang中是以树的形式存在,树的根context为一个空context,我们可以调用context包提供的Background()方法或TODO()方法创建一个根context,两者方法没有太大的区别,context树上的节点context退出时,其子context也会退出。

func Background() Context {
    return backgroundCtx{}
}

可取消的context

我们调用WithCancel方法,向其传入一个父context,方法会给我们一个绑定上的子context,父context取消退出时,该子context也会退出取消。

func WithoutCancel(parent Context) Context {
    if parent == nil {
       panic("cannot create context from nil parent")
    }
    return withoutCancelCtx{parent}
}

可定时取消的context

WithTimeout方法与WithCancel方法传入的参数相比,多了一个时间参数,返回的context不仅跟随父context,当我们传入的时间长度到了后该context也会取消退出。

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
    return WithDeadline(parent, time.Now().Add(timeout))
}

Goang语言的context包还提供了WithDeadline方法,该方法与WithTimeout方法类似,但区别是传入的时间不是时间间隔而是具体截止时间,到了该时间或收到父context的取消时间,该context均会取消退出。

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
    return WithDeadlineCause(parent, d, nil)
}

携带key-value键值对的Context

与前面方法得到的context不同,WithValue方法返回的context不提供退出机制,其主要作用是携带键值对信息用于父子协程间信息的传递,如果要加上退出机制常常与可取消的context或可定时取消的context结合使用。

func WithValue(parent Context, key, val any) Context {
    if parent == nil {
       panic("cannot create context from nil parent")
    }
    if key == nil {
       panic("nil key")
    }
    if !reflectlite.TypeOf(key).Comparable() {
       panic("key is not comparable")
    }
    return &valueCtx{parent, key, val}
}