Context

描述

Context 是并发安全的,它是一个接口,可以手动、定时、超时发出取消信号、传值等功能,主要是用于控制多个协程之间的协作、取消操作


方法

  1. type Context interface {
  2. Deadline() (deadline time.Time, ok bool)
  3. Done() <-chan struct{}
  4. Err() error
  5. Value(key interface{}) interface{}
  6. }
  • Deadline 方法:可以获取设置的截止时间,返回值 deadline 是截止时间,到了这个时间,Context 会自动发起取消请求,返回值 ok 表示是否设置了截止时间。
  • Done 方法:返回一个只读的 channel ,类型为 struct{}。如果这个 chan 可以读取,说明已经发出了取消信号,可以做 清理操作,然后退出协程,释放资源。多次调用 Done 方法会返回同一个 Channel。
  • Err 方法:返回Context 被结束的原因,它只会在 Done 方法对应的 Channel 关闭时返回非空的值。
    1. 如果 context.Context 被取消,会返回 Canceled 错误;
    2. 如果 context.Context 超时,会返回 DeadlineExceeded 错误;
  • Value 方法:获取 Context 上绑定的值,是一个键值对,通过 key 来获取对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据。

    Context 树

    我们并不需要自己去实现 Context 接口,Go 语言提供了函数来生成不同的 Context,通过这些函数可以生x成一颗 Context 树,这样 Context 就可以关联起来,父级 Context 发出取消信号,子级 Context 也会发出,这样就可以控制不同层级的协程退出

    生成根节点

  1. emptyCtx是一个int类型的变量,但实现了context的接口。emptyCtx没有超时时间,不能取消,也不能存储任何额外信息,所以emptyCtx用来作为 context 树的根节点。
  2. 但是我们一般不直接使用emptyCtx,而是使用由emptyCtx实例化的两个变量(background 、todo),分别通过调用Background和TODO方法得到,但这两个 context 在实现上是一样的。

    Background和TODO方法区别

    只是用于不同场景下:Background通常被用于主函数、初始化以及测试中,作为一个顶层的context,也就是说一般我们创建的context都是基于Background;而TODO是在不确定使用什么context的时候才会使用。
    context 包中提供的 context.Backgroundcontext.TODOcontext.WithDeadlinecontext.WithValue 函数会返回实现该接口的私有结构体

    生成树的函数

  3. 可以通过 context。Background() 获取一个根节点 Context。

  4. 有了根节点后,再使用以下四个函数来生成 Context 树:
    • WithCancel(parent Context):生成一个可取消的 Context。
    • WithDeadline(parent Context, d time.Time):生成一个可定时取消的 Context,参数 d 为定时取消的具体时间。
    • WithTimeout(parent Context, timeout time.Duration):生成一个可超时取消的 Context,参数 timeout 用于设置多久后取消
    • WithValue(parent Context, key, val interface{}):生成一个可携带 key-value 键值对的 Context。

      Context 取消多个协程

      如果一个 Context 有子 Context,在该 Context 取消时,其下的所有子 Context 都会被取消。


      Context 传值

      Context 不仅可以发出取消信号,还可以传值,可以把它存储的值提供其他协程使用。
      1. ctx, stop := context.WithCancel(context.Background())
      2. //传值
      3. valCtx := context.WithValue(ctx, "position","gopher")
      4. //使用
      5. position := valCtx.Value("position")

      Context 使用原则

  • Context 不要放在结构体中,需要以参数方式传递
  • Context 作为函数参数时,要放在第一位,作为第一个参数
  • 使用 context。Background 函数生成根节点的 Context
  • Context 要传值必要的值,不要什么都传
  • Context 是多协程安全的,可以在多个协程中使用