源码看 golang context

2021/11/5 22:13:43

本文主要是介绍源码看 golang context,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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,否则找到根节点才返回



这篇关于源码看 golang context的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程