golang Context for goroutines
概要
golang 的提供的 channel 机制是基于 CSP(Communicating Sequencial Processes)模型的并发模式.
通过 channel, 可以很方便的写出多个 协程 (goroutine)之间协作的代码, 将顺序的代码改成并行的代码非常简单. 改造成并行的代码之后, 虽然可以更好的利用多核的硬件, 有效的提高代码的执行效率, 但是, 也带来了代码控制的问题.
并行的代码显然比顺序执行的代码更难于管理和控制, 这时, 就得靠 golang 提供的 Context 接口来帮助我们控制 goroutine 了.
goroutine 的控制
goroutine 之间最基本的控制, 就是通过 channel 来交互数据:
1 func routineSample() {
2 ch := make(chan int, 10)
3 go p1(ch)
4 go c1(ch)
5 go c2(ch)
6
7 time.Sleep(10 * time.Second)
8 }
9
10 func p1(ch chan int) {
11 fmt.Println("Parent go routine!")
12
13 for i := 0; i < 10; i++ {
14 ch <- i
15 }
16
17 close(ch)
18 }
19
20 func c1(ch chan int) {
21 fmt.Println("Child 1 go routine!")
22 for c := range ch {
23 fmt.Printf("child 1, recivie: %d\n", c)
24 time.Sleep(100 * time.Millisecond)
25 }
26 }
27
28 func c2(ch chan int) {
29 fmt.Println("Child 2 go routine!")
30 for c := range ch {
31 fmt.Printf("child 2, recivie: %d\n", c)
32 time.Sleep(100 * time.Millisecond)
33 }
34 }
上述是最基本的示例, p1 函数不断向 channel 中发送数据, c1 和 c2 负责处理数据. 虽然通过 channel 实现 c1, c2 的并发很简单, 但是可以看出, p1 要想控制 c1, c2 没有那么容易.
这时, 就需要通过 Context 接口来控制并发的协程.
取消控制
取消控制指的是任务的发起者, 在特定条件下, 发送信号指示已经接受到任务的协程停止任务的执行.
1 func routineSample() {
2 ch := make(chan int, 10)
3 ctx, cancel := context.WithCancel(context.Background())
4 go p1(ctx, ch)
5 go c1(ctx, ch)
6 go c2(ctx, ch)
7
8 // 300 ms之后取消任务
9 time.Sleep(300 * time.Millisecond)
10 cancel()
11
12 time.Sleep(10 * time.Second)
13 }
14
15 func p1(ctx context.Context, ch chan int) {
16 fmt.Println("Parent go routine!")
17
18 for i := 0; i < 10; i++ {
19 ch <- i
20 }
21
22 close(ch)
23 }
24
25 func c1(ctx context.Context, ch chan int) {
26 fmt.Println("Child 1 go routine!")
27 for c := range ch {
28 select {
29 case <-ctx.Done():
30 fmt.Println("child 1, return!")
31 return
32 default:
33 fmt.Printf("child 1, recivie: %d\n", c)
34 }
35 time.Sleep(100 * time.Millisecond)
36 }
37 }
38
39 func c2(ctx context.Context, ch chan int) {
40 fmt.Println("Child 2 go routine!")
41 for c := range ch {
42 select {
43 case <-ctx.Done():
44 fmt.Println("child 2, return!")
45 return
46 default:
47 fmt.Printf("child 2, recivie: %d\n", c)
48 }
49 time.Sleep(100 * time.Millisecond)
50 }
51 }
300 毫秒后, 发送取消任务的信号 cancel() , c1 和 c2 通过 select 判断是否有取消信号, 收到取消信号后, 退出处理.
通过执行结果可以看出, c1 和 c2 大约处理 5~6 个任务之后停止退出.
超时控制
除了取消控制, context 还有超时的控制.
1 func routineSample() {
2 ch := make(chan int, 10)
3 ctx, _ := context.WithTimeout(context.Background(), 300*time.Millisecond)
4 go p1(ctx, ch)
5 go c1(ctx, ch)
6 go c2(ctx, ch)
7
8 time.Sleep(10 * time.Second)
9 }
10
11 func p1(ctx context.Context, ch chan int) {
12 fmt.Println("Parent go routine!")
13
14 for i := 0; i < 10; i++ {
15 ch <- i
16 }
17
18 close(ch)
19 }
20
21 func c1(ctx context.Context, ch chan int) {
22 fmt.Println("Child 1 go routine!")
23 for c := range ch {
24 select {
25 case <-ctx.Done():
26 fmt.Println("child 1, return!")
27 return
28 default:
29 fmt.Printf("child 1, recivie: %d\n", c)
30 }
31 time.Sleep(100 * time.Millisecond)
32 }
33 }
34
35 func c2(ctx context.Context, ch chan int) {
36 fmt.Println("Child 2 go routine!")
37 for c := range ch {
38 select {
39 case <-ctx.Done():
40 fmt.Println("child 2, return!")
41 return
42 default:
43 fmt.Printf("child 2, recivie: %d\n", c)
44 }
45 time.Sleep(100 * time.Millisecond)
46 }
47 }
控制超时的 WithTimeout 也返回一个 cancel 函数, 可以在超时到达之前来取消任务的执行, 上面的例子等待超时时间达到后自动取消任务, 没有使用 cancel 函数.
goroutine 之间的传值
一般来说, goroutine 之间通过 channel 传递都是业务数据, 除此之外, 还可以通过 channel 来传递一些控制 goroutine 的元数据.
1 func routineSample() {
2 ch := make(chan int, 10)
3 // 哪个goroutine收到5号任务, 就退出, 不再做后续的任务
4 ctx := context.WithValue(context.Background(), "finish", 5)
5 go p1(ctx, ch)
6 go c1(ctx, ch)
7 go c2(ctx, ch)
8
9 time.Sleep(10 * time.Second)
10 }
11
12 func p1(ctx context.Context, ch chan int) {
13 fmt.Println("Parent go routine!")
14
15 for i := 0; i < 10; i++ {
16 ch <- i
17 }
18
19 close(ch)
20 }
21
22 func c1(ctx context.Context, ch chan int) {
23 fmt.Println("Child 1 go routine!")
24 for c := range ch {
25 if c == ctx.Value("finish").(int) {
26 fmt.Println("child 1, return!")
27 return
28 }
29 fmt.Printf("child 1, recivie: %d\n", c)
30 time.Sleep(100 * time.Millisecond)
31 }
32 }
33
34 func c2(ctx context.Context, ch chan int) {
35 fmt.Println("Child 2 go routine!")
36 for c := range ch {
37 if c == ctx.Value("finish").(int) {
38 fmt.Println("child 2, return!")
39 return
40 }
41 fmt.Printf("child 2, recivie: %d\n", c)
42 time.Sleep(100 * time.Millisecond)
43 }
44 }
上面的例子是在 context 中放置一个 key="finish" 的任务号, 如果 c1 或者 c2 收到的任务号和它相同, 则退出任务的执行. 通过运行上面的例子可以看出, c1 或者 c2 执行到 5 号任务的时候就会退出协程. 但是谁收到 5 号任务是不确定的, 多执行几次上面的代码, 可以发现有时是 c1 退出, 有时是 c2 退出.
总结
context 是控制并发协程的上下文, 利用 context, 可以大量简化控制协程的超时, 取消协程执行, 以及协程之间传值的代码. context 很方便, 但也不能乱用, 通过 channel 传递的业务数据, 不要放在 context 中来传递.
此外, context 是线程安全的, 可以放心的在多个协程中使用.
golang Context for goroutines的更多相关文章
- Golang Context 详细介绍
Golang context 本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分. ps: 作者本着开源分享的精神撰写本篇文章,如果 ...
- Golang Context 包详解
Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...
- 带小伙伴手写 golang context
前言 - context 源码 可以先了解官方 context.go 轮廓. 这里捎带保存一份当前 context 版本备份. // Copyright 2014 The Go Authors. Al ...
- Golang Context 的原理与实战
本文让我们一起来学习 golang Context 的使用和标准库中的Context的实现. golang context 包 一开始只是 Google 内部使用的一个 Golang 包,在 Gola ...
- 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
ex1 package main import ( "fmt" ) // 最佳context 实践 // Context 目标是实现各个goroutine能及时终止退出. func ...
- Golang context包解读
Context 通常被译作 上下文 ,一般理解为程序单元的一个运行状态.现场.快照,而翻译中 上下 又很好地诠释了其本质,上下上下则是存在上下层的传递, 上 会把内容传递给 下 . 在Go语言中,程序 ...
- golang context 剖析 1.7.4 版本
1. 内部结构之 - timerCtx . type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. dead ...
随机推荐
- python安装MySQLclient
直接使用pip命令安装mysqlclient : pip3 install mysqlclient 如果windows安装不了MySQL-python mysqlclient 参考以下解决方案: 这个 ...
- Debian9安装Metasploit
参考博文:https://www.jianshu.com/p/ea0629b9e367 0x0 添加Kali源 deb http://http.kali.org/kali kali-rolling m ...
- 为什么ISR4K、ASR1K等设备的QoS ACL没有显示计数?
思科的ISR4K和ASR1K设备都是IOS XE的架构,它们和传统的IOS架构是不一样的. 以ISR4K为例,和一般的IOS(例如ISR G2)有所区别,他的转发更依赖硬件完成,针对NAT或QoS应用 ...
- office 2016
Excel 2016: F4 : 重复上一步操作. 例子: 如果上一步是合并单元格, 则 再次选中其他几个单元格, F4即再次完成合并. 单元格中插入对角线: 选中单元格, 右键--设置单元格格式- ...
- mac 重启php-fpm
查看php-fpm端口是否在被php-fpm使用 一般修改 php.ini 文件后经常需要重启php-fpm sudo killall php-fpm // 关闭 再输入 sudo lsof -i:9 ...
- CF908D 【New Year and Arbitrary Arrangement】
蒟蒻渣渣禹小心翼翼发布题解.... 这道题,嗯,期望,dp,好,我们有思路了.... however, 主要问题在于字符串无限延伸,so,我们需要考虑记录前缀的关键量来为DP设置终止状态. 我们不妨设 ...
- Red Black Tree(红黑树)
(修改于 2018-05-06 15:53:22 还差删除维护操作.层序遍历没完成.维护操作没完成不想写层序遍历怎么办...) 今天下午完成了红黑树的插入的维护操作,但删除的维护操作还没有解决,删除的 ...
- 第二节: Vuejs常用特性1
一. 常用特性 1. 表单元素 通过 v-model指令绑定 输入框.单选/多选框.下拉框.文本框 2. 表单域修饰符 (1) .number:转换成数值,如果输入的是非数字字符串时,无法进行转换 ( ...
- ThinkPHP5 动态生成图片缩略图
需求场景 不同终端(PC端.手机端.平板),不同界面(列表页.详情页),对图片大小的要求不一样, 如果所有场景下都使用同一尺寸的图片,势必对会网络带宽及服务器性能造成一定的影响,由此需要服务器端能够根 ...
- 操作系统OS - 常见系统的调度算法
https://en.wikipedia.org/wiki/Scheduling_(computing) 在wiki的下面可以找到