golang context 剖析 1.7.4 版本
1. 内部结构之 - timerCtx 。
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu. deadline time.Time
}
- 里面有一个 timer,用来触发超时之后的 回调函数,也就是超时之后,帮你 cancel 一下。理论上,你不用在结构体里存一份这个,这里存了这个指针,主要是用来取消这个定时触发,基本上就是因为一个定时器你如果不需要了,就要马上回收这个资源,否则会很耗资源的。例如,假设,你有一个十分钟的超时触发机制,不过由于某些原因,这个context被其他东西cancel了,那么这十分钟内,内部的定时器还要继续工作,这难道不是很浪费么,就像你们几个人玩游戏,让一个人记录分数,这时你们上司来了,这个游戏完不成了,难道那个记录分数的同事还傻乎乎的继续记么,,,,很明显太二了,所以要及时的终止这个计时器。
- 这个deadline就是那个预设的超时的绝对时间。同时,实现了Context的 Deadline接口。
- 关键就是里面的 cancelCtx,首先这是一个结构体,不是接口,也不是指针。可以从这个源码里来思考思考,什么时候在struct里面直接用struct,什么时候在里面埋一个interface,什么时候在里面用一个 *struct。把timerCtx分开成两个单词,一个是timer,一个是Context,那么上面两个字段承载了有关【time】的东西,那么可以猜到,这个cancelCtx就承载了【Context】的功能。所以来看看这个cancelCtx是个什么鬼?
1.1 cancelCtx 的结构分析。
type cancelCtx struct {
Context done chan struct{} // closed by the first cancel call. mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
- 先来插一句,代码如何抽象,当然是见仁见智,但是这里把cancelCtx放在 timerCtx里面,而且是以 struct的形式,没有用指针,也没有用interface,我认为,,我认为是因为,这个cancelCtx是属于timerCtx的一部分,正是这一部分体现了,timerCtx 确实是一个Context。
- done字段:作为一个Context,必须要有 Done()方法,这个方法要返回一个 chan struct{}, 那么很明显,这个字段就是来干这个的。
- err字段: 同上,作为一个Context,必须要有 Err()方法,,,返回一个错误,很明显,这个字段就是用来干这个的。
- mu字段: 用来保证并发操作的。
- children字段:golang 的 context 包用一种 【父-子】的关系来管理【一个业务需求】引发的【各个子goroutine】所关联的各个【Context】,很明显,这个children字段用来记录,谁是我的儿子。
- Context字段:这是一个接口,用来存放【父Context】。注意到,要想成为 Context必须实现四个方法。最后一个方法就是Value() 。我们发现,现在为止只有三个被实现了,那就是Deadline,Done,Err。那么谁来实现Value呢。。。就是这个埋进来的Context了。也就是说,你作为一个timerCtx,你仅仅有【时间】的概念,你不应该有【Value】的概念,【Value】的概念,你从你的【父Context】那里直接伸手去要,恩,大概就这个意思。{插:这个Context包还讲解了golang的面向对象风格是啥么样的形态,所以说看源码涨知识啊。。}。
- context包提供了内部cancel的机制,主要就是靠这个cancelCtx结构体,它提供了一个私有的 cancel 方法来处理这个事情。
- 当在外部调用 WithCancel WithTimeout WithDeadline 三个方法时,其实都是新建了一个cancelCtx。
- 看一下 cancel方法:
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
close(c.done)
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock() if removeFromParent {
removeChild(c.Context, c)
}
}
- 第一个事实,全包只有两个cancel方法,第一个就是这个cancelCtx提供的cancel方法,而第二个,是timerCtx提供的cancel方法,后者也是在前者的基础上多做了一些关于定时器的资源的回收而已。
- cancel方法接收两个参数,第一个是 是否从【父Context】上删除这个子节点,第二个是写出你为啥要cancel这个Context。
- context包的作者设计的是,只要Err是空,那么这个Context就还没有被cancel掉,只要Err不为空,就说明Context已经被cancel掉了。所以这个方法里有关于这个的监测部分。
- cancel方法做了这样几件事情。【一,将传入的错误记录到Context的err中,标志一下,我已经cancel了。】【二,close(c.done)关闭这个通道,这在外部是可知的,通过Done()接口返回的channel就是在这里关闭的。】【三,递归的将{子Context}全部cancel掉,并将自身的children字段清空。】【四,根据removeChile参数的值,从而决定是否将自身从{父Context}那里清除掉。】
- 顺便看看removeChild这个函数。基本上就是从【父Context】的children字段里,delete自身。
- 再来顺便看看timerCtx的cancel方法,看看增加了什么功能:
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)//为啥这里要传false?
if removeFromParent {//为啥一定在这里去处理removeChild这个事情?
// 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()
}
- 可以看到很蹊跷的一点,为什么 removeFromParent 这个过程一定要在这里做,而不能这样【c.cancelCtx.cancel(removeFromParent,err)】?好,答案就是removeChild函数接受两个参数,一个是【父Context】,一个是【自身】,这就是语义。当一个timerCtx想要从【父Context】那里清除自己的时候,【自身】这个语义代表的就是这个timerCtx,而不是timerCtx内部的cancelCtx。另外,从removeChild的代码来看,其实就是从一个map里delete一个key,当时在timerCtx初始化的时候,存入的key就是timerCtx,所以在removeChild的出后,当然要delete自身这个timerCtx。
- 除了上述,timerCtx的cancel方法仅仅是多了个对于定时器的资源的回收而已,无他耳。
1.2 图形化这整个结构
- 方块表示timerCtx,圆形表示cancelCtx(除了backgroundctx之外)。
- 实线箭头表示 【父Context的指向】。
- 虚线箭头表示 【子Context的指向】。
- 箭头的起点都是从圆形的cancelCtx出发,表示无论是 【父Context】还是【子Context】,这两者信息都存在 cancelCtx这个内部结构中。
- 箭头的终点都是方块,表示【父Context】和【子Context】都是指最外部的结构,上图中也就是这个timerCtx。
- 这个图不包括 WithCancel 和 WithValue 函数得到的Context。
1.3 propagateCancel函数的分析
// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]bool)
}
p.children[child] = true
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
- 第一,这个函数在整个包里被两个函数所调用到,两个函数里都是在初始化新的Context的时候用到这个函数。
- 第二,函数的注释说明了,这个函数用来负责这样一个事情,当 【父Context】被cancel的时候,【子Context】也必须被cancel,这个机制。
- 所以,第一句很明显,如果【父Context】不会被cancel的话,那么也就不需要这个propagate的过程了,就直接return。更进一步发现,如果一个Context的Done()返回的是nil,在这个包的设计思想里面,也就是相当于这个Context不会被cancel。这两者就是同义的。{Done()==nil} 同义于{Context不会被cancel}。
- 接下来的事情应该是这样,应该把新生成的这个Context放到【父Context】的【children】字段里,只有这样,才能让【父Context】来管理各【子Context】。
- 而上文和上图中都已经说过了,cancelCtx里的children字段就是来管理【子Context】的。
- 所以,我先得获得 【父Context】内部的【cancelCtx】,恩,这就是 parentCancelCtx 函数的作用,,就是获得一个【父Context】内部的【cancelCtx】。
- 由于 valueCtx的存在(后文说明),导致【父Context】并不一定内部就有一个cancelCtx,所以,要一直往上找,注意看上面的图,一直沿着这棵树往上找,直到找到一个【Context】内部具有【cancelCtx】。好了,我新建的这个Context,和我找到的这个【父Context】就在这一步产生了【父子】的关系。
- 假设一直找,一直找,都找不到一个内部拥有【cancelCtx】的家伙当【父】,那么看这段代码的最后:
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
- (??? ) 新开一个goroutine,监听两个channel,一个就是看看【父Context】的Done是否已经关了,如果关了,这个【子】也就直接cancel得了,并且不用告诉【父】了,因为你根本就没有在【父】的【children】里存在过,,,,,,属于年三十的兔子,有你过年,没你也过年。
- (???) 第二个监听的是新建的这个【子Context】的Done(),如果这个已经关闭了,那么就直接退出。
- (???) 感觉整个程序根本走不到这个go func里来,因为一开头已经判断了,如果parent.Done()==nil,则直接退出。如果parent.Done!=nil,那么,整个Context的树,肯定有一个节点是内部支持cancelCtx的,那么也依然不会走到这个go func() 里,,,。。我哪里有疏忽的地方,请各位看官指正,这一块。。。。。。
golang context 剖析 1.7.4 版本的更多相关文章
- 带小伙伴手写 golang context
前言 - context 源码 可以先了解官方 context.go 轮廓. 这里捎带保存一份当前 context 版本备份. // Copyright 2014 The Go Authors. Al ...
- Golang Context 的原理与实战
本文让我们一起来学习 golang Context 的使用和标准库中的Context的实现. golang context 包 一开始只是 Google 内部使用的一个 Golang 包,在 Gola ...
- golang context包
go context标准库 context包在Go1.7版本时加入到标准库中.其设计目标是给Golang提供一个标准接口来给其他任务发送取消信号和传递数据.其具体作用为: 可以通过context发送取 ...
- Golang Context 详细介绍
Golang context 本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分. ps: 作者本着开源分享的精神撰写本篇文章,如果 ...
- Golang Context 包详解
Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...
- golang context学习记录1
1.前言 一个请求,可能涉及多个API调用,多个goroutine,如何在多个API 之间,以及多个goroutine之间协作和传递信息,就是一个问题. 比如一个网络请求Request,需要开启一些g ...
- 【GoLang】golang context channel 详解
代码示例: package main import ( "fmt" "time" "golang.org/x/net/context" ) ...
- Golang context包解读
Context 通常被译作 上下文 ,一般理解为程序单元的一个运行状态.现场.快照,而翻译中 上下 又很好地诠释了其本质,上下上下则是存在上下层的传递, 上 会把内容传递给 下 . 在Go语言中,程序 ...
- golang 性能剖析pprof
作为一个golang coder,使用golang编写代码是基本的要求. 能够写出代码,并能够熟悉程序执行过程中各方面的性能指标,则是更上一层楼. 如果在程序出现性能问题的时候,可以快速定位和解决问题 ...
随机推荐
- Struts2 让跳转指定执行某个方法
很多时候,我们想让jsp页面中的某个超链接,点击后执行后台的某个方法,里面该如何做呢? 这里方法很多种 我举例两种: 1.在struts.xml配置,配置如下: <package name=&q ...
- linux学习之路(4)
用户身份与文件权限 通过uid来区分: 管理员 UID 为 0:系统的管理员用户. 系统用户 UID 为 1-999: Linux 系统为了避免因某个服务程序出现漏洞而被黑客提 权至整台服务器,默认 ...
- rsync服务搭建--2018.5.8 [优化后最终版]
2018年5月8日 22:09:38 第一步配置基础环境(按照自己的规划配置并非每人的环境都一致) 第一台服务器(RSYNC服务器): rsync外网地址:10.0.0.41 rsync内网地址:1 ...
- ecliplse集成SVN
按照下图操作 : SVN不同版本下载链接在文章底部有提供 上图点击add之后稍等一会就会弹出下图: 上图点击next之后: 最后等待完成之后重启ecliplse即可 重启ecliplse之后显示SVN ...
- linux虚拟机安装mysql(Mysql-5.7.10)
注:MySQL5.5版本开始弃用了常规的configure编译方法,通过cmake来编译.需要下载安装cmake编译器.boost库.ncurses库.GNU分析器生成器bison 1. 安装基础环境 ...
- MongoDB 副本集配置,开启账号认证
MongoDB 自带功能强大的主从,配置也很简单,从零开始花了30分钟搞定 3台以上机器IP: 192.168.1.24, 192.168.1.25, 192.168.1.26, 192.168.1. ...
- linux下vim python代码自动补全
一.vim python自动补全插件:pydiction 可以实现下面python代码的自动补全: 1.简单python关键词补全 2.python 函数补全带括号 3.python 模块补全 4.p ...
- 设置使用的python版本
一.查看当前使用的python版本,或设置使用的python版本 二.python2中默认使用ASCII码,无法识别中文,报错如图,解决办法,设置字符集为utf-8
- loj #6121. 「网络流 24 题」孤岛营救问题
#6121. 「网络流 24 题」孤岛营救问题 题目描述 1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩.瑞恩被关押在一个迷宫里,迷宫地形复杂, ...
- 在libuv中使用openssl建立ssl连接
在libuv中使用openssl建立ssl连接 @(blogs) 使用openssl进行加密通信时,通常是先建立socket连接,然后使用SSL_XXX系列函数在普通socket之上建立安全连接,然后 ...