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 ...
随机推荐
- EXPOSE ocker run -P 时,会自动随机映射 EXPOSE 的端口
EXPOSE 声明端口 格式为 EXPOSE <端口1> [<端口2>...]. EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用 ...
- 吴裕雄--天生自然Numpy库学习笔记:NumPy 从已有的数组创建数组
import numpy as np x = [1,2,3] a = np.asarray(x) print (a) import numpy as np x = (1,2,3) a = np.asa ...
- iOS 开发中常用的排序(冒泡、选择、快速、插入、希尔、归并、基数)算法
1.冒泡排序: 冒泡算法是一种基础的排序算法,这种算法会重复的比较数组中相邻的两个元素.如果一个元素比另一个元素大(小),那么就交换这两个元素的位置.重复这一比较直至最后一个元素.这一比较会重复n-1 ...
- JavaScript中关于隐式转换的一些总结
JavaScript运算符中的隐式转换规律:一.递增递减运算符(前置.后置)1.如果包含的是有效数字字符串或者是有效浮点数字符串,则会将字符串转换(Number())为数值,再进行加减操作,返回值的类 ...
- Interesting丨当我们用蚂蚁的视角看待世界
分享一组很有意思的图片~
- Java日期时间API系列10-----Jdk8中java.time包中的新的日期时间API类的DateTimeFormatter
1.DateTimeFormatter final修饰,线程安全,用于打印和解析日期-时间对象的格式化程序. 创建DateTimeFormatter: DateTimeFormatter dateTi ...
- 操作系统OS - 常见系统的调度算法
https://en.wikipedia.org/wiki/Scheduling_(computing) 在wiki的下面可以找到
- Servlet 学习(六)
会话 1.定义 一般意义会话:指两人以上的对话(多用于学习别种语言或方言时) 计算机中的会话:客户端和服务器的通讯 web客户端 A ------>Tomcat web客户端 B ------& ...
- C++ STL之动态数组vector(⽮量)的使⽤
写再最前面:摘录于柳神的笔记: 之前C语⾔⾥⾯⽤ int arr[] 定义数组,它的缺点是数组的⻓度不能随⼼所欲的改变,⽽C++⾥⾯有⼀个能完全替代数组的动态数组 vector (有的书⾥⾯把它翻 ...
- Java记录2---包的使用
javac -d . A.java -d 表示自动生成包层 . 表示这个包层在当前目录下建立 package link.roland;//package 语句必须是第一条语句 //该语句表示把该文件中 ...