源码分析context的超时及关闭实现

前言:

Golang的context的作用就不多说了,就是用来管理调用上下文的,控制一个请求的生命周期。golang的context库里有四个组件。 withCancel用来控制取消事件,withDeadline和withTimeout是控制超时,withValue可以传递一些key value。 

// xiaorui.cc

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

func WithValue(parent Context, key, val interface{}) Context

分析context:

下面的结构图就很形象的说明了各个context 的关联关系。context节点通过children map来连接 子context节点。总之,context节点是层层关联的。

该文章后续仍在不断的更新修改中, 请移步到原文地址  http://xiaorui.cc/?p=5604

上面的功能介绍倒是没什么,让我好奇的是go context是怎么实现的超时和链式关闭。究其原理,还是要分析context的源代码,WithTimeout也是通过WithDeadline来实现的。

// xiaorui.cc

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

通过下面的WithDeadline方法,我们可以分析出创建一个子context及定时器过程。

// xiaorui.cc

type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
        ...
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),   // 返回一个子的context
		deadline:  d,
	}
        //  添加父节点和子节点的关联关系
	propagateCancel(parent, c)
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded) // deadline has already passed
		return c, func() { c.cancel(true, Canceled) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
                //  添加定时器
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
        // 返回 context 和 关闭的方法
	return c, func() { c.cancel(true, Canceled) }
}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

context是怎么链式关闭的? 先前分析源码的时候整错了,以为会new一个协程来监听父节点的context的存活。

// xiaorui.cc

func propagateCancel(parent Context, child canceler) {
    ...
	} else {
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

但想来golang在1.9后就内置了标准库context, 它不能这么粗暴,现在golang社区里开源库都离不开context方法。为了关闭子节点context,new一个goroutine来监听父节点,这个太不合理。。。想来一定是没有仔细分析代码。。。果然。。。看错了。

// xiaorui.cc

func (c *timerCtx) cancel(removeFromParent bool, err error) {
        ...
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
                // 关闭定时器
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {
	p, ok := parentCancelCtx(parent)
	if !ok {
		return
	}
	p.mu.Lock()
	if p.children != nil {
		delete(p.children, child)  // 从父层里删除子节点
	}
	p.mu.Unlock()
}

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
         ...
	c.mu.Lock()
        ...
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done) // 关闭
	}
        // 层层关闭 ...
	for child := range c.children {
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

        // 从父context child里删除子context节点
	if removeFromParent {
		removeChild(c.Context, c)
	}
}

总结:

context的源码很简单,代码也精简的,有兴趣的朋友可以细细的琢磨下。


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc