一、包说明分析

context包:这个包分析的是1.15

contextContext/request-scoped
servercanceled
CancelFunc

使用Context包的程序需要遵循以下以下规则,目的是保持跨包兼容, 已经使用静态分析工具来检查context的传播:

Contextnil Contextcontext.TODOrequest-scoped

二、包结构分析

核心的是:

Context

还有两个暴露的变量:

Canceled

context取消时由Context.Err方法返回

DeadlineExceeded

context超过deadline时由Context.Err方法返回

三、Context接口类型分析

context也称上下文.

先看说明:

Context

方法集分析:

Deadline

  • 返回的是截至时间
  • 这个时间表示的是任务完成时间
  • 到这个时间点,Context的状态已经是be canceled(完成状态)
  • ok为false表示没有设置deadline
  • 连续调用,返回的结果是相同的

Done

ContextConetext

Err

  • Done还没关闭(此处指Done返回的只读信道),Err返回nil
  • Done关闭了,Err返回non-nil的error
  • Context是be canceled,Err返回Canceled(这是之前分析的一个变量)
  • 如果是超过了截至日期deadline,Err返回DeadlineExceeded
  • 如果Err返回non-nil的error,后续再次调用,返回的结果是相同的

Value

  • 参数和返回值都是interface{}类型(这种解耦方式值得学习)
  • Value就是通过key找value,如果没找到,返回nil
  • 连续调用,返回的结果是相同的
  • 上下文值,只适用于跨进程/跨api的request-scoped数据
  • 不适用于代替函数可选项
  • 一个上下文中,一个key对应一个value
  • 典型用法:申请一个全局变量来放key,在context.WithValue/Context.Value中使用
  • key应该定义为非暴露类型,避免冲突
  • 定义key时,应该支持类型安全的访问value(通过key)
  • key不应该暴露
    • 表示应该通过暴露函数来进行隔离(具体可以查看源码中的例子)

四、后续分析规划

Context

withCancel:

  • CancelFunc
  • newCancelCtx
    • cancelCtx
  • canceler
  • propagateCancel
    • parentCancelCtx

以下是分析出的通过规则:很多包对外暴露的是接口类型和几个针对此类型的常用函数. 接口类型暴露意味可扩展,但是想扩展之后继续使用常用函数,那扩展部分就不能 修改常用函数涉及的部分,当然也可以通过额外的接口继续解耦. 针对"暴露接口和常用函数"这种套路,实现时会存在一个非暴露的实现类型, 常用函数就是基于这个实现类型实现的.在context.go中的实现类型是emptyCtx. 如果同时需要扩展接口和常用函数,最好是重新写一个新包.

下面的分析分成两部分:基于实现类型到常用函数;扩展功能以及如何扩展.

五、基于实现类型到常用函数

Context接口的实现类型是emptyCtx. 

emptyCtxcontext.stringercontext.stringer
emptyCtxBackground()/TODO()
Context
cancelCtx/myCtx/myDoneCtx/otherContext/ timeCtx/valueCtxemptyCtx
context.Context

cancelCtx

支持取消信号的上下文

看下方法:

cancelCtxKey
internal/reflectlite.TypeOf

值得注意的是String()不属于Context接口的方法集,而是emptyCtx对 context.stringer接口的实现,cancelCxt内嵌的Context,所以不会覆盖 emptyCtx对String()的实现. 

cancel()
removeChild
cancelCtx.Context
children的cancel()children

非暴露的构造函数.

回顾一下:cancelCtx添加了Context对取消信号的支持. 只要触发了"取消信号",使用方只需要监听done信道即可.

myCtx myDoneCtx otherContext属于测试,等分析测试的时候再细说.

timerCtx

前面说到了取消信号对应的上下文cancelCtx,timerCtx就是基于取消信号上下扩展的

注释说明:内嵌cancelCtx是为了复用Done和Err,扩展了一个定时器和一个截至时间, 在定时器触发时触发cancelCtx.cancel()即可.

timerCtx内嵌了cancelCtx,说明timerCtx也实现了canceler接口, 从源码中可以看出,cancel()是重新实现了,String/Deadline都重新实现了.

cancel()中额外添加了定时器的停止操作.

deadline

回顾一下: Context的deadline是机会取消信号实现的.

valueCtx

valueCtx
ValueString
valueCtx.val

六、With-系列函数

支持取消信号 WithCancel:

ContextcancelCtxCancelFunc
Context

小技巧:

default

这个会等待,因为没有加default.

background
WithCancel

支持截至日期 WithDeadline:

 

deadline
emptyCtx/cancelCtx/timerCtx/valueCtx,WithDeadline

WithCancel上面已经分析了,派生一个支持取消信号的Context,并将父Context的取消信号 传播到派生Context(ps:这么说有点绕,简单点讲就是将派生Context添加到父Context的children), 下面看看第一个构造支持deadline的过程.

time.AfterFunc
WithDeadline

因为timerCtx是内嵌了cancelCtx,所以有一个派生Context是可以同时支持取消和deadline的, 后面的value支持也是如此.

WithDeadline的注释说明: 派生Context的deadline不晚于参数,如果参数晚于父Context支持的deadline,使用父Context的deadline, 如果参数指定的比父Context早,或是父Context不支持deadline,那么派生Context会构造一个新的timerCtx. 父Context的取消/派生Context的取消/或者deadline的过期,都会触发取消信号对应的操作执行, 具体就是Done()信道会被关闭.

WitchTimeoutWithDeadline

支持值WitchValue:

valueCtx
ContextContextdeadline

到目前为止,只了解了包的内部实现(顶层Context的构造/With-系列函数的派生), 具体使用,需要看例子和实际测试.

ps:一个包内部如何复杂,对外暴露一定要简洁.一个包是无法设计完美的,但是约束可以, 当大家都接受一个包,并接受使用包的规则时,这个包就成功了,context就是典型.

对于值,可以用WithValue派生,用Value取; 对于cancel/deadline,可以用WithDeadline/WithTimeout派生,通过Done信号获取结束信号, 也可以手动用取消函数来触发取消操作.整个包的功能就这么简单.

七、扩展功能以及如何扩展

扩展功能现在支持取消/deadline/value,扩展这个层级不应该放在这个包, 扩展Context,也就是新建Context的实现类型,这个是可以的, 同样实现类型需要承载扩展功能,也不合适.

cancelercancelCtx/timerCtx

剩下的就是Value扩展成多kv对,这个主要还是要看应用场景.

八、补充

DeadlineExceededDeadlineExceeded

再看看net.Error接口:

context中的DeadlineExceeded默认是实现了net.Error接口的实例. 这个是为后面走网络超时留下的扩展.