Golang context
Context 是golang中的上下文,用于服务器处理请求相关的协程之间,处理其中的超时中断,取消操作等情况。
来自源码的解释:
对服务器的传入请求应该创建一个上下文,而对服务器的传出调用应该接受一个上下文。 它们之间的函数调用链必须传播 Context,可以选择将其替换为使用 WithCancel、WithDeadline、WithTimeout 或 WithValue 创建的派生 Context。 当一个上下文被取消时,从它派生的所有上下文也被取消。
WithCancel、WithDeadline 和 WithTimeout 函数采用 Context(父级)并返回派生的 Context(子级)和 CancelFunc。调用 CancelFunc 会取消子项及其子项,删除父项对子项的引用,并停止任何关联的计时器。未能调用 CancelFunc 会泄漏子项及其子项,直到父项被取消或计时器触发。 go vet 工具检查 CancelFuncs 是否用于所有控制流路径。
不要将 Context 存储在结构类型中; 相反,将 Context 显式传递给需要它的每个函数。 Context 应该是第一个参数,通常命名为 ctx:
func DoSomething(ctx context.Context, arg Arg) 错误 {
// ... 使用 ctx ...
}
相同的 Context 可以传递给运行在不同 goroutine 中的函数; Context 对于多个 goroutine 同时使用是安全的。
Context接口:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Context 是接口类型,明确了各种context应实现的方法。
Deadline() 返回context的任务取消截至时间,如果没有设定,ok 返回false。
Done() 返回一个chan, 如果context不会被取消,返回nil,否则,当context取消时,可以从done返回的chan中读取数据。
Err(), 如果done被关闭,则返回非空err,说明取消原因,否则返回nil。
Value() 可以在协程间共享一些kv数据,通过Value()获取值。 使用 value只用于request范围内相关的数据, 而不是为了做函数的可选参数。
代码大纲
Context的代码非常少,结构也非常简单
Context.Err()返回的两种错误
// 主动取消,用于cancelCtx
var Canceled = errors.New("context canceled")
// 超时取消,用于timerCtx
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
四种Context
实现了Context接口的有以下四种context:
// 是空的contex
type emptyCtx int
// 可主动取消的Context
type cancelCtx struct {}
// 可定时取消的Context
type timerCtx struct {}
// 可传递key,value的Context
type valueCtx struct {}
空Context
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
emptyCtx 是空 context, Background() 和TODO() 分别返回一个emptyCtx,
Background() 通常用于主函数,初始化,以及测试,作为top-level context。
TODO() 用于某个函数需要传递context,而你不知道传递什么时候。
即使函数允许,也不要传递 nil Context。 如果您不确定要使用哪个 Context,请传递 context.TODO。
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
emptyCtx 除实现Context接口的四个函数外,还实现了可被fmt.Print()打印的String()函数。
cancelCtx
cancelCtx 是一个可被取消的context, 当被取消时,它会取消它的所有子context。
type cancelCtx struct {
Context // 用Context来表示父节点
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // 用于在取消时,取消所有子context
err error // set to non-nil by the first cancel call
}
func (c *cancelCtx) Value(key interface{}) interface{} {
...
}
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
Done() 返回一个chan,当 cancelCtx 取消时,此chan会被关闭,所以chan接收端会接收到nil数据,从而得知 cancelCtx 被取消。
func (c *cancelCtx) Err() error {
...
}
type stringer interface {
String() string
}
func contextName(c Context) string {
if s, ok := c.(stringer); ok {
return s.String()
}
return reflectlite.TypeOf(c).String()
}
func (c *cancelCtx) String() string {
return contextName(c.Context) + ".WithCancel"
}
和 emptyCtx 一样,也是多实现了一个String()方法
WithCancel
通过WithCancel可以创建一个cancelCtx, 同时还会返回一个cancel函数,cancel函数会关闭cancelCtx.done, 也会cancel每一个子context, 同时还会从父 context中移除自己。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
// 使child在parent取消时被取消
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
if done == nil {
return // parent永远不会被取消
}
select {
case <-done:
// parent 已经被取消
child.cancel(false, parent.Err())
return
default:
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent 已经被取消
child.cancel(false, p.err)
} else {
// 加入到parent的 children中
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else { // 祖先没有 cancelCtx
atomic.AddInt32(&goroutines, +1)
// 启动协程,为parent善后
go func() {
select {
// 源码此处没有注释,个人理解,虽然祖先没有cancelCtx,但不意味着Done()没有返回管道,因为如果Context是可以用户定义的,所有chlid也需要检测用户自定义的Done()。
case <-parent.Done():
// 因为祖先没有cancelCtx, 所以不需要从parent中移除自己,所以传递false
child.cancel(false, parent.Err())
case <-child.Done(): // 这个的原因是,如果child被取消了,那么此协程会发送资源泄露,所有child的Done也要检测
}
}()
}
}
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
// 如果祖先节点存在一个cancelCtx, (timerCtx也是cancelCtx)
if !ok {
return nil, false
}
p.mu.Lock()
ok = p.done == done
p.mu.Unlock()
if !ok {
return nil, false
}
return p, true
}
propagateCancel(parent Context, child canceler) 用于建立父子关系,以至于某一个祖先 context取消时,能够取消自己。
其中 parentCancelCtx(parent Context) (*cancelCtx, bool) 会找到一个为cancelCtx的祖先节点,如果没有的话,返回false。
高级处:cancelCtx的Value会对key进行判断,所以查找第一个为cancelCtx的祖先节点,parent.Value(&cancelCtxKey).(*cancelCtx) 即可以做到。
func (c *cancelCtx) Value(key interface{}) interface{} {
if key == &cancelCtxKey {
return c
}
return c.Context.Value(key)
}
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
cancel
cancel函数取消此cancelCtx, 同时也会调用所有子context的cancel函数,并将自己与parent解除父子关系
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()]
// 已经cancel
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
// 关闭c.done
if c.done == nil {
/*
* var closedchan = make(chan struct{})
*
* func init() {
* close(closedchan)
* }
*/
c.done = closedchan
} else {
close(c.done)
}
// 遍历关闭子 context
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
// gc 子context
c.children = nil
c.mu.Unlock()
// 从parent context中移除自己
if removeFromParent {
removeChild(c.Context, c)
}
}
timerCtx
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
timerCtx也是cancelCtx, 同时增加了用于定时的timer,以及标识过期时间的deadline。
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
c.deadline.String() + " [" +
time.Until(c.deadline).String() + "])"
}
WithDeadline
根据父 parent,创建一个可取消的 context.
如果设置的取消时间晚于parent的deadline,那么直接与parent关联即可,因为parent取消时会取消自己。
否则设置deadline为指定的取消时间。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// parent 的deadline早于指定的d
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 建立与祖先之间取消关系
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // 已经到了取消时间
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// 设置过期取消函数,并定时
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
cancel
func (c *timerCtx) cancel(removeFromParent bool, err error) {
// 执行cancelCtx的cancel,循环取消子context
c.cancelCtx.cancel(false, err)
if removeFromParent {
// 从parent从移除自己
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
valueCtx
type valueCtx struct {
Context
key, val interface{}
}
多了一对kv。valueCtx不是cancelCtx,不可取消,没有deadline, done()等
WithValue
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
WithValue用于创建一个附带数据的valueCtx。由于valueCtx的Context是父节点,所以假设祖先节点有valueCtx,那么valueCtx的Value(key interface{})也可以找到祖先节点的key,value
func stringify(v interface{}) string {
switch s := v.(type) {
case stringer:
return s.String()
case string:
return s
}
return "<not Stringer>"
}
func (c *valueCtx) String() string {
return contextName(c.Context) + ".WithValue(type " +
reflectlite.TypeOf(c.key).String() +
", val " + stringify(c.val) + ")"
}
String()函数
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
自下向上查找key对应的value,直到找到指定的key,否则找到根节点才返回