博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Golang 并发,有缓存通道,通道同步案例演示
阅读量:6893 次
发布时间:2019-06-27

本文共 2808 字,大约阅读时间需要 9 分钟。

学习Golang差不多半年了,go中的并发,通道,通道同步单个来讲都不陌生,但是结合在一起运用的时候就有些懵逼,同时也不知道为何要这么做。我想这是初学者都会遇到的困惑,在这里讲下自己的理解。

为什么用通道而不是共享变量

看一段代码

func main() {	var a int	for i := 0; i < 10; i++ {		go func() {			for i := 0; i < 100; i++ {			    a++			}		}()	}	time.Sleep(1 * time.Second)	fmt.Print(a)}// 运行结果PS C:\Users\mayn\go\src\test_5_5> go run .\main.go1000复制代码

从运行结果来看,主线程是可以跟协程共享变量的,同时10个协程分别自加100次,得到1000的结果与预期结果一样

现在增加每个协程的运算量,再看一下运行结果

func main() {	var a int	for i := 0; i < 10; i++ {		go func() {			for i := 0; i < 100000; i++ {				a++			}		}()	}	time.Sleep(1 * time.Second)	fmt.Print(a)}// 输出结果PS C:\Users\mayn\go\src\test_5_5> go run .\main.go213897PS C:\Users\mayn\go\src\test_5_5> go run .\main.go206400PS C:\Users\mayn\go\src\test_5_5> go run .\main.go211926复制代码

可以看到每个协程由100的自加变为100000的自加,此时输出结果每次都不同并且与1000000的预期结果相差很大,个人没有深入研究只是简单推测由于并发的异步特性,同一时间有多个协程执行了自增,实际cpu只计算了一次,这种误差会随着并发协程的数量和各自计算量的增多而变大。(后来有人补充cpu核数限制为1核就不会发生这种并行的情况)

使用有缓存的通道得出正确结果

func main() {	var ch = make(chan int, 10)	for i := 0; i < 10; i++ {		go func() {			var a int			for i := 0; i < 100000; i++ {				a++			}			ch <- a		}()	}	var sum int	func() {		for i := 0; i < 10; i++ {			sum += <- ch		}	}()	fmt.Print(sum)}// 输出结果PS C:\Users\mayn\go\src\test_5_5> go run .\main.go1000000复制代码

大致思路还是开启10个协程,同时将原来定义在主线程中的变量a定义到每个协程中,在主线程中定义有10个缓冲的通道。这时每个协程各自处理自己的运算结果互不干扰,只在最后将各自运算结果写入到通道中。主线程再遍历通道进行读操作,只有当协程中有数据被写入时才能读取到数据并且汇总结果。由于读操作是在主线程中会发生阻塞,所以此时可以去掉睡眠,程序依然能正确执行,这就是通道同步。

如果通道读操作也开一个协程来处理会怎么样

func main() {	var ch = make(chan int, 10)	for i := 0; i < 10; i++ {		go func() {			var a int			for i := 0; i < 100000; i++ {				a++			}			ch <- a		}()	}	var sum int	go func() {		for i := 0; i < 10; i++ {			sum += <- ch		}	}()	fmt.Print(sum)}// 输出结果PS C:\Users\mayn\go\src\test_5_5> go run .\main.go0复制代码

很明显如果读操作也开协程,此时主线程不会发生阻塞,主线程不等协程结束直接结束了,想要得到正确结果,主要主线程等待就行了。这样做的优点就是读操作也是并发的,不需要同步等待。

协程与主线程共享变量

还是这段代码,加上时间等待。

func main() {	var ch = make(chan int, 10)	for i := 0; i < 10; i++ {		go func() {			var a int			for i := 0; i < 100000; i++ {				a++			}			ch <- a		}()	}	var sum int	go func() {		for i := 0; i < 10; i++ {			sum += <- ch		}	}()	time.Sleep(1 * time.Second)	fmt.Print(sum)}// 输出结果PS C:\Users\mayn\go\src\test_5_5> go run .\main.go1000000复制代码

细心观察,可以发现并发通道读操作的结果使用了主线程的变量sum,程序按预期正确执行。这就说明了协程是可以跟主线程共享变量的,只是使用的前提是这个变量只被一个协程使用,如果被多个协程使用就可能出现文章开头出现的问题。

假如主线程与协程同时操作一个变量

func main() {	var a int	go func() {		for i := 0; i < 1000000; i++ {			a++		}	}()	for i := 0; i < 1000000; i++ {		a++	}	time.Sleep(1 * time.Second)	fmt.Print(a)}// 输出PS C:\Users\mayn\go\src\test_5_5> go run .\main.go1079312PS C:\Users\mayn\go\src\test_5_5> go run .\main.go1003960PS C:\Users\mayn\go\src\test_5_5> go run .\main.go1021828复制代码

发现即使只有单一的协程与主线程共享变量,也是会发生问题。结论:协程间尽量不要共享变量,很难保证不出问题。说这么多只是体现通道的作用与优点。

以上全部内容只是个人的一点摸索,不代表完全正确。

转载于:https://juejin.im/post/5cd8ca97e51d453b0d1c24c4

你可能感兴趣的文章
为什么要学习Python?
查看>>
VMWareStation 12和CentOS 6.5 三节
查看>>
关于微信小程序登陆的问题
查看>>
从零开始的linux 第六章
查看>>
ssh远程登录
查看>>
10.28 rsync工具介绍 10.29/10.30 rsync常用选项 10.31 rsync
查看>>
手机web页面制作时的注意事项
查看>>
LINUX系统服务与管理(Services)---------第二天
查看>>
【Docker篇之一】Docker镜像及容器
查看>>
Linux 通过配置Cobbler服务器全自动批量安装部署
查看>>
单片机编程入门学习 这几问你能回答吗?
查看>>
在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。未找到或......
查看>>
【转】[行业透视] 外企九年-我最终选择放弃
查看>>
最终目标展示:一个完善的操作系统
查看>>
opencv图像融合(给人脸添加一个眼镜)
查看>>
mysql参数优化辅助工具之tuning-primer.sh
查看>>
SpringBoot之整合MyBatis
查看>>
docker 笔记
查看>>
我的友情链接
查看>>
云储存将成未来大规模视频监控储存主要模式
查看>>