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

rfyiamcool · · 187 次点击 · · 开始浏览    

前言:

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


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节点通过children map来连接 子context节点。总之,context节点是层层关联的。

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


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

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


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的存活。


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来监听父节点,这个太不合理。。。想来一定是没有仔细分析代码。。。果然。。。看错了。


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的源码很简单,代码也精简的,有兴趣的朋友可以细细的琢磨下。

end.


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

本文来自:峰云就她了

感谢作者:rfyiamcool

查看原文:源码分析context的超时及关闭实现

入群交流(该群和以上内容无关):Go中文网 QQ交流群:731990104 或 加微信入微信群:274768166 备注:入群; 公众号:Go语言中文网

187 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传