一、包说明分析
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接口的实例. 这个是为后面走网络超时留下的扩展.