context最佳实践
本内容来源于作者的学习笔记,地址
另外:
我记得当时大学时学习谭浩强老师的C
语言时有一句话,C
语言程序的最小组成单位是函数。而在golang
中提到函数第一反应就是error
,其次就是context
。为什么这么说呢?无论BS
还是CS
都需要网络交互,而且在golang
中有着天然的并发性,因此如何去管控链路上的每个节点,以及子节点如何感知链路上的事件尤为重要。
golang
采用树结构来描述链路,在树上传递事件,该事件用来表述父节点已经终止对任务的处理了,子节点的存在是为了辅助父节点完成任务,因此当子节点接收到事件后应当终止任务的执行。
你了解context树的动作吗
context.Context
是一个接口,而且它的实现类型都有一个匿名的context.Context
成员,比如:
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
由于匿名成员的特性,因此就算cancelCtx
没有定义任何方法,那么它也已经实现了context.Contetx
接口。其实cancelCtx
(WithCancel
,WithTimeout
和WithDeadline
的底层依赖,至于为什么后面会提到),timerCtx
(WithTimeout
和WithDeadline
的底层实现),valueCtx
(WithValue
的底层实现)都没有全部自己实现context.Context
中的全部方法,都使用到了上面说到的匿名成员的特性。
here是一篇源码分析,里面给出了每个成员的实现,这里给出结果。
Deadline | Value | Done | Err | |
---|---|---|---|---|
根节点 | √ | √ | √ | √ |
cancelCtx | × | × | √ | √ |
timerCtx | √ | × | × | × |
valueCtx | × | √ | × | × |
除了上面这个表格,你还需要知道关于每个节点的一些细节:
- 根节点的所有方法返回值都是返回参数的零值
- 当对节点
X
执行cancel
方法时- 以
X
节点为根的整棵树脱离context
树 - 以
X
节点为根的整棵树上所有的"cancelCtx"
- 通道都会被关闭
err
都会被赋值
- 以
X
节点为根的整棵树上所有的节点Done
方法会立即接受到事件
- 以
timerCtx
本质上个还是cancelCtx
,通过goroutine
自动调用cancel
- 在创建
timerCtx
的时候,如果指定的时间小于等于当前父节点Deadline
结果则会转变为cancelCtx
- 只要调用
WithValue
就会生成一个valueCtx
在知道了这些细节和上述表格后你就能解决使用context
的所有疑问。
最佳实践
nil context
要求:不要将nil
赋值给context.Context
。
原因:有些标准库和第三方库内部在识别context
为nil
的时候会直接panic
。
key的选择
要求:不要使用内置类型,使用自定义私有类型。
推荐:type $key struct{}
,节省内存,方便扩展。
原因:细节5可能会导致树中的valueCtx
可能有相同的key
,context
可能都是使用者传入的,开发者无法保证key
的唯一性,因此可能会有非预期行为,因此使用定制的私有类型,保证程序的可控性。
value的类型
要求:传递多个信息到下游时,使用map类型,而不是创建多个节点。
原因:通常跨进程传输数据时,会传输大量的信息,如果每个值都使用一个context
节点,会使得context
树很臃肿,而且信息分散在各个context
节点,因此通常将要传输的信息打包到一个map
然后放在一个context
节点中。
value的并发安全
要求:value的内容是只读的。
原因:context 是并发安全的,但是当通过 value 获取到的数据可能存在并发安全的问题,如果值的类型是指针或者引用类型,就会面临并发安全的问题。上下文用于描述历史,历史中已经存在的内容不可变更,历史只会不断的增长。
超时控制
要求:使用父节点创建新的节点时,不需要考虑祖先节点的超时时间。
原因:细节4确保内部会通过节点类型变换保证父节点的Deadline
大于子节点。
有疑问加站长微信联系(非本文作者)
