context.Context

sync.WaitGroup类型是一个实现一对多goroutine协作流程的同步工具。还有另一种工具也可以实现这种协作流程。

回顾sync.WaitGroup实现协作流程

在使用WaitGroup的时候,建议是用“先统一Add,再并发Done,最后Wait”的模式来构建协作流程。要避免并发的调用Add方法。这就带来一个问题,需要在一开始就能确定执行子任务的goroutine的数量,至少也是在启动goroutine之前。
下面是一个示例,稍微做了一些改造:

package mainimport ("time""fmt""sync""sync/atomic"
)func coordinateWithWaitGroup() {total := 12var num int32var wg sync.WaitGroup// 定义好goroutine中返回前要执行的defer函数deferFunc := func() {wg.Done()}for i := 0; i < total; i++ {wg.Add(1)go addNum(&num, i, deferFunc)}wg.Wait()
}// 这个函数的defer函数通过参数来给出
func addNum(numP *int32, id int, deferFunc func()) {defer deferFunc()for i := 1; ; i++ {currNum := atomic.LoadInt32(numP)newNum := currNum + 1time.Sleep(time.Millisecond * 200)if atomic.CompareAndSwapInt32(numP, currNum, newNum) {fmt.Printf("id: %02d 第 %02d 次更新num成功: %d\n", id, i, newNum)break}}
}func main() {coordinateWithWaitGroup()
}

这里的改造是为了更像之后要使用context包时的用法,不过在使用规则上还是满足WaitGroup的要求的。

通过context包实现协作流程

这里就是要在写一个coordinateWithWaitContext函数,来代替上面的coordinateWithWaitGroup函数。两个函数要具有相同的功能。
这里先直接给出示例代码了:

func coordinateWithWaitContext() {total := 12var num int32cxt, cancelFunc := context.WithCancel(context.Background())// 定义好goroutine中返回前要执行的defer函数,这里用到了上面的cancelFuncdeferFunc := func() {if atomic.LoadInt32(&num) == int32(total) {cancelFunc()}}for i := 0; i < total; i++ {go addNum(&num, i, deferFunc)}<- cxt.Done()
}

所有的变化都在上面这个函数里了。这里先后调用了context.Background函数和context.WithCancel函数。得到了一个可撤销context.Context类型的值,赋值给了变量cxt。还有一个context.CancelFunc类型的撤销函数,赋值给了变量cancelFunc。
这里在判断goroutine执行完毕的依据是通过判断num里的值。一旦判断完成,就会调用之前准备好的cancelFunc函数,此时cxt.Done函数返回的通道就会接收到值,结束等待。

和WaitGroup的比较
WaitGroup需要事先知道所有goroutine的数量,而context这里更关心是否满足某个条件,一旦条件满足就可以退出。
这里我想提一下python,让我想到了python中的for循环和while循环。能用for循环就不要用while循环。使用while循环可能由于条件判断复杂了,造成条件永远无法满足而成了死循环。使用for循环的话就没有这个问题了。不过当循环的退出和数量没有关系时,只能用while循环了。
就好比WaitGroup,如果可以通过goroutine的数量判断,那么应该还是使用WaitGroup好。如果遇到结束条件和goroutine数量无关的时候,就只能用context了。

context.Context类型

context.Context类型,是在Go 1.7发布时才被加入到标准库的。而后,标准库中的很多其他代码包都为了支持它而进行了扩展,包括:os/exec包、net包、database/sql包、runtime/pprof包和runtime/trace包,等等。
之所以会收到众多代码包的积极支持,主要因为它是一种非常通用的同步工具。它的值不但可以任意的扩散,而且还可以被用来传递额外的信息和信号。就是Context类型可以提供一个代表上下文的值,之类值是并发安全的,也就是说它可以被传播给多个goroutine。

接口类型
Context最新实际是一个接口类型,在context包中实现该接口的所有私有类型,都是基于某个数据类型的指针类型。所以,如此传播并不会影响该类型值的功能和安全。

可繁衍的
Context类型的值是可以繁衍的,这意味着可以通过一个Context值产生出任意个子值。这些子值可以携带父值的属性和数据,也可以相应通过其父值传达的信号。如此,所有的Context值共同构成了一颗代表了上下文全貌的属性结构。树的根节点是一个已经在context包中预定义好的context值,它是全局唯一的。通过调用context.Background函数,就可以获取到它。

包内的函数
在context包中,包含了4个用于繁衍Context值的函数:

  • WithCancel,产生一个可撤销的parent的子值
  • WithDeadline,产生一个会定时撤销的parent的子值
  • WithTimeout,同上,也是定时撤销的parent的子值
  • WithValue,产生一个会携带额外数据的parent的子值

这些函数的第一个参数类型都是context.Context,而名称都为parent。顾名思义,这个位置上的参数对应的都是产生Context值的父值。

撤销信号在上下文树中的传播

context包中的WithCancel、WithDeadline和WithTimeout都是被用来基于给定的COntext值产生可撤销的子值的。

WithCancel
这个函数在被调用后,产生两个结果值。第一个是可撤销的Context值,第二个是用于触发撤销信号的函数。
撤销函数被调用后,对应的Context值会先关闭它内部的接收通道,通道关闭了接收该通道的操作就会立即返回,就是Done方法返回的那个通道。然后,它还会向它的所有子值传达撤销信号。这些子值如果还有子值,就会一级一级把撤销信号传递下去。最后,这个Context值会断开它与其父值之间的关联。

WithDeadline和WithTimeout
通过调用WithDeadline函数或者WithTimeout函数生成的Context值也是可撤销的。它们不但可以被手动撤销,还会依据在生成是给定的过期时间,自动地进行定时撤销。这里的定时撤销功能是借助它们内部的计时器来实现的。
当过期时间到达时,两种Context值的行为与手动撤销是的行为是几乎一致的,只是多了一步停止并释放掉内部的计时器。
WithDeadline和WithTimeout是相似的。都是通过设置,会在某个时间自动触发,就是ctx.Done()能够取到值。差别是,DeadLine是设置一个时间点,时间对上了就到期。Timeout是设置一段时间,比如几秒,过个这段时间,就超时。其实底层的Timeout也是通过Deadlin实现的。

WithValue
这个函数得到的值是不可撤销的。撤销信号在传播时,若遇到它们会直接跨过,并试图将信息直接传给它们的子值。

传递数据

通过WithValue函数产生新的Context值的时候需要3个参数:父值、键和值。这里键必须是可判断等的,类似字典的键。不过Context值并不是用字典来存储键和值的,而是简单地存储在父值相应的字段中。
通过Value方法,可以获取数据。在调用包含属性的Context值的Value方法是,会先判断给定的键,如有有就返回存储的值,否则会到其父值中继续查找,会一直沿着上下文根节点的方法一直查找。因为其他几种Context值都是无法携带数据的,所以Value方法在查找的时候,会跨过这这些Context值。

无法改变数据
Context接口没有提供改变数据的方法,所以通常只能通过在上下文数中添加含数据的Context值来存储新的数据,或者通过撤销此种值的父值丢弃掉相应的数据。如果存储在这里的数据可以从外部改变,那么必须自信保证安全。

下面这个示例展示了Context值里数据的传递:

package mainimport ("context""fmt""time"
)type myKey intfunc main() {keys := []myKey{myKey(20),myKey(30),myKey(60),myKey(61),}values := []string{"value in node2","value in node3","value in node6","value in node6Branch",}rootNode := context.Background()node1, cancelFunc1 := context.WithCancel(rootNode)defer cancelFunc1()node2 := context.WithValue(node1, keys[0], values[0])node3 := context.WithValue(node2, keys[1], values[1])fmt.Printf("The value of the key %v found in the node3: %v\n",keys[0], node3.Value(keys[0]))fmt.Printf("The value of the key %v found in the node3: %v\n",keys[1], node3.Value(keys[1]))fmt.Printf("The value of the key %v found in the node3: %v\n",keys[2], node3.Value(keys[2]))fmt.Println()node4, cancelFunc4 := context.WithCancel(node3)defer cancelFunc4()node5, cancelFunc5 := context.WithTimeout(node4, time.Hour)defer cancelFunc5()fmt.Printf("The value of the key %v found in the node5: %v\n",keys[0], node5.Value(keys[0]))fmt.Printf("The value of the key %v found in the node5: %v\n",keys[1], node5.Value(keys[1]))fmt.Println()node6 := context.WithValue(node5, keys[2], values[2])fmt.Printf("The value of the key %v found in the node6: %v\n",keys[0], node6.Value(keys[0]))fmt.Printf("The value of the key %v found in the node6: %v\n",keys[2], node6.Value(keys[2]))fmt.Println()node6Branch := context.WithValue(node5, keys[3], values[3])fmt.Printf("The value of the key %v found in the node6Branch: %v\n",keys[1], node6Branch.Value(keys[1]))fmt.Printf("The value of the key %v found in the node6Branch: %v\n",keys[2], node6Branch.Value(keys[2]))fmt.Printf("The value of the key %v found in the node6Branch: %v\n",keys[3], node6Branch.Value(keys[3]))fmt.Println()node7, cancelFunc7 := context.WithCancel(node6)defer cancelFunc7()node8, cancelFunc8 := context.WithTimeout(node7, time.Hour)defer cancelFunc8()fmt.Printf("The value of the key %v found in the node8: %v\n",keys[1], node8.Value(keys[1]))fmt.Printf("The value of the key %v found in the node8: %v\n",keys[2], node8.Value(keys[2]))fmt.Printf("The value of the key %v found in the node8: %v\n",keys[3], node8.Value(keys[3]))
}

总结

Context类型是一个可以实现多goroutine协作流程同步的工具。还可以通过它的值传达撤销信号或传递数据。
Context类型的值大体可分3种:

  • 根Context值
  • 可撤销的Context值
  • 含数据的Context值

所有的Context值共同构成了一颗上下文树。这棵树的作用域是全局的,根Context值就是树的根,它也是全局唯一的,并且不提供任何额外的功能。
包含数据的Context值不能被撤销,可撤销的Context值又无法携带数据。但是,由于它们共同组成了一个有机的整体,即上下文数,所以在功能上要比sync.WaitGroup强大的多。

这个系列偏重理论,就少了很多实际的应用,关于context包,我之前还有一篇:
https://blog.51cto.com/steed/2330218
在这篇里介绍了两个主要功能:

  • 控制超时时间
  • 保存上下文数据

转载于:https://blog.51cto.com/steed/2347877

Go36-32-context.Context相关推荐

  1. android this context,Android應用開發中關於this.context=context的理解

    在Android應用開發中,有的類里面需要聲明一個Context的成員變量,然后還需要在該類的構造函數中加上this.context=context;這行代碼.為什么要這么寫呢?不寫不行么? 先看下面 ...

  2. Hadoop中Context类的作用和Mapper<LongWritable, Text, Text, LongWritable>.Context context是怎么回事【笔记自用】

    问题导读: 1.Context能干什么? 2.你对Context类了解多少? 3.Context在mapreduce中的作用是什么? 下面我们通过来源码,来得到Context的作用: 下面主要对Set ...

  3. mybatis-plus 错误java.lang.NoClassDefFoundError: org/apache/velocity/context/Context

    https://blog.csdn.net/qq_39609151/article/details/82855305 mybatis-plus 错误java.lang.NoClassDefFoundE ...

  4. MybatisPlus报错: org.apache.velocity.context.Context(已解决)

    MybatisPlus报错: org.apache.velocity.context.Context(已解决) 报错如图所示: 原因是缺少了依赖,解决方案如下: pom.xml文件当中加入veloci ...

  5. Go context.Context的学习

    一.前言 Golang context是Golang应用开发常用的并发控制技术,它与WaitGroup最大的不同点是context对于派生goroutine有更强的控制力,它可以控制多级的gorout ...

  6. context.Context

    在golang控制并发(sync.WaitGroup和context.Context)中,我们有讲到context的作用及简单使用,现在扩展开来讲讲context还有哪些别的东西 Context 接口 ...

  7. Golang context.Context

    这里填写标题 1. Golang context.Context 1.1. 内容前导 1.2. 基础知识 1.2.1. Context 接口 1.2.2. 顶层 Context 1.2.3. 子 Co ...

  8. mybatis-plus自动生成的时候报错java.lang.NoClassDefFoundError: org/apache/velocity/context/Context

    当使用mybatisplus的代码自动生成的时候报错 09:02:44.188 [main] DEBUG com.baomidou.mybatisplus.generator.AutoGenerato ...

  9. Context context = getApplicationContext()

    使用getApplicationContext 取得的是当前app所使用的application,这在AndroidManifest中唯一指定.意味着,在当前app的任意位置使用这个函数得到的是同一个 ...

  10. Exception in thread “main“ java.lang.NoClassDefFoundError: org/apache/velocity/context/Context at c

    11:41:33.067 [main] DEBUG com.baomidou.mybatisplus.generator.AutoGenerator - ======================= ...

最新文章

  1. RecyclerView Adapter中notifyDataSetChanged 的作用
  2. php 正则教程,最通俗易懂的php正则表达式教程(上)
  3. iOS_根据文字字数动态确定Label宽高
  4. 修改注释里的作者名字
  5. Space-Filling Designs
  6. java获得电脑性能_Java:使用SingletonStream获得性能
  7. 在Android关机中插入脚本
  8. 极客大学架构师训练营 微服务网关 领域驱动设计 DDD OAuth 2.0 中台架构 第20课 听课总结
  9. SSh三大框架工作原理介绍
  10. java带圈数字,小1,小2
  11. 国民体质测定标准计算机应用软件,体测标准计算器
  12. ZigBee网络数据传递流程_基于ZigBee远程通信的水质监测系统设计
  13. SAP供应商主数据中税号1-5的用途
  14. 2020年SEM小搜投放指南:竞价小渠道如何把效果做到极致
  15. Win 10 任务栏中Google开启时出现两个Google图标
  16. 体检先锋_家居健康小秘笈
  17. Python编程入门之Arcade游戏编程(一)
  18. 用php开发扑克小游戏网页版,开发日记:KBEngine+Unity+php做个扑克小游戏(一)
  19. 2010年法定假期安排时间表
  20. ant批量修改文件名_Ant Renamer-Ant Renamer(免费批量重命名工具)下载 v2.12官方版--pc6下载站...

热门文章

  1. JavaScript逻辑运算符
  2. java连接设备连接给参数_如何通过蓝牙连接两个设备按参数发送配对代码? JAVA,Android的...
  3. ddddocr打包不成功解决办法
  4. Navicat安装(图文教程)
  5. libiconv字符集转换库使用方法
  6. oracle 关于归档的视图,10G中,什么视图可以看归档空间的大小
  7. 湘乡江南计算机学校潘银,湘潭市教育局督查组来湘乡督查合格学校建设
  8. mysql的脚本默认存_MySQL修改默认存储引擎的实现方法
  9. linux 循环显示所有的sh.*文件.,利用shell脚本遍历文件夹内所有的文件并作整理统计的方法...
  10. 果园机器人的写作思路_《果园机器人》教学设计3篇