go context 包

整个 context 包原码就有一个文件即 context.go,我估计 context 的设计就是由 http 引出来的,学习 context 其实也是学习 go 的接口设计范式。

1.1   Context 接口

type Context interface {
	// 取消树和过期时间的实现
	Deadline() (deadline time.Time, ok bool) // 过期时间点
	Done() <-chan struct{} // 过期或者手动取消时关闭通道
	Err() error // 可以判断是过期还是手动取消
	

	// 键值对的实现,valueCtx类型真正实现
	Value(key interface{}) interface{}
}

从这点可以看出,Context 库设计的目的: (1) 退出通知机制 (2) 传递数据

1.2   四个重要的类型

  • emptyCtx 类型:无实际作用的 int 型,这是最原始的 Context 接口实现;
  • cancelCtx 类型:Context + 取消树(这里的“取消树”是一个完整的名词,“取消树”上的键是 canceler 类型,说明它可能是 cancelCtx,也可能是 timerCtx);
  • timerCtx 类型:cancelCtx + 过期时间;在上下级的超时时间中,下级超时时间一定要比上级早,否则即使用 WithDeadline() 创造出来的也是 cancelCtx,这还是很容易理解的。有一个 timer 属性,是用于提前取消定时,即调用 Stop() 方法;
  • valueCtx 类型:Context + 键值对。

实例化上述四个类型的API函数:

  • Background()、TODO():构造 emptyCtx;
  • WithCancel():构造 cancelCtx;
  • WithDeadline()、WithTimeout():构造 timerCtx;一个是时间点,一个是时间段,时间段会转化为时间点;定时用了 time.AfterFunc 函数,返回值存储在 timer 属性;
  • WithValue():构造valueCtx。

在实例化中,有两条主线: (1) 第一条线是context,上面四个类型是一个衔接一个。后期由 removeChild() 负责找到父节点并从其 children 移除该节点。所以其特点就是向上追溯父节点。 Background(a框) > WithCancel(b框) > WithTimeout(c框,超时3秒) > WithTimeout(d框,超时6秒) > WithValue(e框);需要注意一点的是 d 框不是 timerCtx,因为其超时比父晚,所以会直接生成 cancelCtx。 context 链 (2) 第二条线是 children,只有 cancelCtx 和 timerCtx 能串成链,如果上级不是这两个类型,再继续向上上级找,直到找到才挂靠。由 propagateCancel() 和 parentCancelCtx() 这两个函数负责该逻辑。所以其特点是就向下一级一级取消子节点。

1.3   重要的内部函数

  • cancel():无论是超时还是手动取消都会调用,DeadlineExceeded 和 Canceled 分别是超时取消和动手取消; (1) cancelCtx 的 cancel():关闭 done 通道,cancel “取消树”上的节点(也就是取消所有子节点),判断要不要从父节点上移除; (2) timerCtx 的 cancel():会先调用 cancelCtx 的 cancel(false, err),判断要不要从父节点上移除,最后 c.timer.Stop(),这也是建议无论是否设置超时都习惯性调用 defer cancel(),否则 c.timer 计时器到时之前不会被回收。
  • propagateCancel():“取消树”的繁衍,或者是“构造”也可以;
  • parentCancelCtx():propagateCancel()调用时会调用到该函数;从当前节点往上查找到最近的父级 cancelCtx 节点,并作为子节点挂靠在其下面。
  • removeChild():从父节点上移除;cancel 是自上而下的,当前的节点 cancel 了,那么其所有子节点也要 cancel,但父节点按原先的逻辑走;先从父节点上移除,等父节点 cancel 的时候就不用再去 cancel 那个已经 cancel 掉的子节点了。