一起学go之Context

前言

在go开发中经常使用到context,使用的时候感觉和其他语言(py的flask)上下文有点类似,所以激起我的好奇心,看看go的context都干了啥。看了下源码不多,所以花了点时间,把源码稍微看了看。

什么是Context?

Context 也叫作“上下文”,一般理解为程序单元的一个运行状态、环境、快照等信息。其中上下是指存在上下层的传递,上会把内容传递给下,程序单元则指的是 Goroutine。

Context的作用

context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等(后面会放出我使用案例)。为了方便以下ctx等价于context

简单例子

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	ticker := time.NewTicker(time.Second * 1)

	defer func() {
		cancel()
		ticker.Stop()
	}()
	for {
		isBreak := false
		select {
		case <-ticker.C:
			fmt.Println("木子林")
		case <-ctx.Done():
			isBreak = true
			fmt.Println(ctx.Err())
		}
		if isBreak{
			break
		}
	}


}

源码梳理

Context接口

Context是Go 语言 context 包对外暴露的接口,该接口定义了四个需要实现的方法:

  1. Deadline() : 返回 context 的截止时间,通过此时间,函数就可以决定是否进行接下来的操作,如果时间太短,就可以不往下做了,否则浪费系统资源。当然,也可以用这个 deadline 来设置一个 I/O 操作的超时时间。

  2. Done(): 返回一个 channel,可以表示 context 被取消的信号 。(PS:当这个 channel 被关闭时,说明 context 被取消了。注意,这是一个只读的channel。 我们又知道,读一个关闭的 channel 会读出相应类型的零值。并且源码里没有地方会向这个 channel 里面塞入值。换句话说,这是一个 receive-only 的 channel。因此在子协程里读这个 channel,除非被关闭,否则读不出来任何东西。也正是利用了这一点,子协程从 channel 里读出了值(ni l)后,就可以做一些收尾工作,尽快退出。)

  3. Err(): 返回一个错误,表示 channel 被关闭的原因,比如说:尚未关闭Done,Err将返回nil;关闭“完成”,Err将返回一个非零错误。

  4. Value(key interface{}):Value返回键对应的值,如果没有值与键关联,则返回nil。

ps:  这四个方法是context最主要的方法,后面所有的代码都基于这四个方法展开

对外方法

1- background

上下文中最顶层的默认值,通常由主函数、初始化和测试使用,并作为传入请求的顶级上下文,作为所有 context 的根节点

2- todo

不清楚要使用哪个上下文,或者它还不可用(因为周围的函数还没有扩展到接受上下文参数)。例如,调用一个需要传递 context 参数的函数,你手头并没有其他 context 可以传递,这时就可以传递 todo,来占个位子,最终要换成其他 context。

内部实现起来比较简单,直接new(emptyCtx)就行了,对外使用Background(), TODO()对外使用(大写外部可调用:context.Background(), context.TODO())

emptyCtx: 实际上是一个空的 context,永远不会被 cancel,没有存储值,也没有 deadline。emptyCtx不是 struct{},因为emptyCtx类型的变量必须有不同的地址

 ps: 从String的方法中可以看出,只返回backgrounders,todo对应信息,其他的都返回未识别

3-WithCancel

WithCancel:方法能够从 Context 中创建出一个新的子上下文,同时还会返回用于取消该上下文的函数,也就是 CancelFunc

WithCancel调用关系图

withCancel 第一步就是new 一个新的 ctx, cancelCtx是一个可以取消的 Context,实现了 canceler 接口。它直接将接口 Context 作为它的一个匿名字段,这样,它就可以被看成一个 Context。

 cancelCtx

Value方法和Err方法没什么好说的,其中Value中的&cancelCtxKey表示cancelCtx为其自身返回的key。 我们下面主要说下Done和cancel方法:

done源码

done 只有调用了 Done() 方法的时候才会被创建,done是懒式加载。函数返回的是一个只读的 channel,而且没有地方向这个 channel 里面写数据。所以,直接调用读这个 channel,协程会被 block 住。一般通过搭配 select 来使用。一旦关闭,就会立即读出零值。 

cancel源码

1: 如果已经取消了,则直接返回

2:关闭c t x,需要将done 设为 已关闭channel或关闭 Done channel

3:递归所有的子级,进行取消

4: 如果removeFromParent为true则从父节点移除自己

propagateCancel:父对象找到可取消的子级

  1. parent.Done() == nil,也就是 parent 不会触发取消事件时,当前函数直接返回;

  2. 当 child 的继承链上有 parent 是可以取消的上下文时,就会判断 parent是否已经触发了取消信号:

    • 如果已经被取消,当前 child 就会立刻被取消;

    • 如果没有被取消,当前 child 就会被加入 parent 的 children 列表中,等待 parent 释放取消信号;

  3. 如果没有可取消的parent,会开启一个新的 Goroutine,同时监听parent.Done()child.Done()两个管道。作用在于:

    • 第一个 case 说明当父节点取消,则取消子节点。如果去掉这个 case,那么父节点取消的信号就不能传递到子节点

    • 第二个 case 是说如果子节点自己取消了,那就退出这个 select,父节点的取消信号就不用管了。如果去掉这个 case,那么很可能父节点一直不取消,这个 goroutine 就泄漏了。当然,如果父节点取消了,就会重复让子节点取消。

parentCancelCtx: 返回父级的基础*cancelCtx

  1. 如果没有子级或者已经取消直接返回

  2. parentCancelCtx返回父级的基础*cancelCtx,查询出来的与Done进行比对

4- WithDeadline

WithDeadline: 创建 timerCtx 上下文的过程中,判断了上下文的截止日期与当前日期,并通过time.AfterFunc 方法创建了定时器,当时间超过了截止日期之后就会调用 cancel 方法同步取消信号

  1. 如果已经过期则直接调用WithCancel
  2. 如果执行到这里过期了,直接取消
  3. 如果还没有存在,则挂载一个定时任务,定时去取消

timerCtx

timerCtx 基于 cancelCtx,只是多了一个 time.Timer 和一个 deadline。Timer 会在 deadline 到来时,自动取消 context

timerCtx 提供了主动取消,当调用主动取消则需要关闭定时。

5- WithTimeout

基于父 context,返回带超时时间的子 context 和取消函数

6- WithValue

WithValue返回和键关联的值为val的父项的副本 ,从父上下文中创建一个子上下文,传值的子上下文使用私有结构体 valueCtx

valueCtx

如果当前 valueCtx 中存储的键与 Value 方法中传入的不匹配,就会从父上下文中查找该键对应的值直到在某个父上下文中返回 nil 或者查找到对应的值。 

官方博客里对context 使用提出了几点建议

  1. 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。

  2. 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。

  3. 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。

  4. 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。

推荐阅读

一起聊聊 Go Context 的正确使用姿势

深度解密Go语言之context

一文吃透 Go 语言解密之上下文 context

上下文 Context

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子林_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值