Context

go Context

上下文 context.Context 一般用于在 API 边界之间以及过程之间传递截止时间、取消信号或者其他请求相关的数据。

一个接口

context.Context 是 Go 语言在 1.7 版本中引入标准库的接口,该接口定义了四个需要实现的方法,其中包括:

  1. Deadline — 返回 context.Context 被取消的时间,也就是完成工作的截止日期;
  2. Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;
  3. Err — 返回 context.Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值;
    1. 如果 context.Context 被取消,会返回 Canceled 错误;
    2. 如果 context.Context 超时,会返回 DeadlineExceeded 错误;
  4. Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据;

四个具体实现

  • emptyCtx

    本质是一个 int。
    emptyCtx 永远不会取消,没有值,也没有截止日期。它不是 struct{},因为此类型的变量必须具有不同的地址。

  • cancelCtx

    type cancelCtx struct {
      Context
    
      mu       sync.Mutex  // 用于保护这几个字段的锁,以保证 cancelCtx 是线程安全的        
      done     chan struct{}  // 用于获取该 Conext 的取消通知     
      children map[canceler]struct{} // 用于存储以当前节点为根节点的所有可取消的 Context,以便在根节点取消时可以把它们一并取消
      err      error  // 用于存储取消时指定的错误信息               
    }
  • timerCtx

    type timerCtx struct {
      cancelCtx
    
      timer *time.Timer // 由 cancelCtx.mu 来保护
      deadline time.Time
    }

    在 cancelCtx 的基础上封装了一个定时器和一个截止时间,这样既可以根据需要主动取消,也可以在到达 deadline 是通过 timer 来触发取消动作。

  • valueCtx

    type valueCtx struct {
      Context
    
      key, val interface{}
    }

六个函数

  • func Background() Context

  • func TODO() Context

    Background 和 TODO 这两个函数内部都会创建 emptyCtx,
    本质上是一样的,主要是语义上的区别。

    Background 主要用于在初始化是获取一个 Context,而 TODO 函数官方文档建议在本来应该使用外层传递的 ctx,而外层却没有传递的地方使用。

  • func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

    用于把一个 Context 包装成一个 cancelCtx,并提供一个取消函数,调用它可以取消对应的 Context。

  • func WithDeadline(parent Context, d time.Time) (ctx Context, cancel CancelFunc)

  • func WithTimtount(parent Context, d time.Duration) (ctx Context, cancel CancelFunc)

  • func WithValue(parent Context, key, value interface{}) Context

    用于上下文传值。

    func (c *valueCtx) Value(key interface{}) interface{} {
      // 用子 context 取父 context 的值时,如果 key 被判等则会出现覆盖的情况,所以最好不要直接使用 string、int 这些基础类型作为 key,而是每一个 Context 的 keys 都要用一种自定义类型包装一下。
      if c.key == key {
        return c.val
      }
      return c.Context.Value(key)
    }

Context 使用原则

  1. 不要把 Context 放在结构体中,要以参数的方式传递
  2. 以 Context 作为参数的函数方法,应该把Context作为第一个参数,放在第一位。
  3. 给一个函数方法传递 Context 的时候,不要传递 nil,如果不知道传递什么,就使用 context.TODO
  4. Context 的 Value 相关方法应该传递必须的数据,不要什么数据都使用这个传递
  5. Context 是线程安全的,可以放心的在多个 goroutine 中传递
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!