内容简介:学习Golang差不多半年了,go中的并发,通道,通道同步单个来讲都不陌生,但是结合在一起运用的时候就有些懵逼,同时也不知道为何要这么做。我想这是初学者都会遇到的困惑,在这里讲下自己的理解。看一段代码从运行结果来看,主线程是可以跟协程共享变量的,同时10个协程分别自加100次,得到1000的结果与预期结果一样
学习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.go 1000 复制代码
从运行结果来看,主线程是可以跟协程共享变量的,同时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.go 213897 PS C:\Users\mayn\go\src\test_5_5> go run .\main.go 206400 PS C:\Users\mayn\go\src\test_5_5> go run .\main.go 211926 复制代码
可以看到每个协程由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.go 1000000 复制代码
大致思路还是开启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.go 0 复制代码
很明显如果读操作也开协程,此时主线程不会发生阻塞,主线程不等协程结束直接结束了,想要得到正确结果,主要主线程等待就行了。这样做的优点就是读操作也是并发的,不需要同步等待。
协程与主线程共享变量
还是这段代码,加上时间等待。
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.go 1000000 复制代码
细心观察,可以发现并发通道读操作的结果使用了主线程的变量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.go 1079312 PS C:\Users\mayn\go\src\test_5_5> go run .\main.go 1003960 PS C:\Users\mayn\go\src\test_5_5> go run .\main.go 1021828 复制代码
发现即使只有单一的协程与主线程共享变量,也是会发生问题。结论:协程间尽量不要共享变量,很难保证不出问题。说这么多只是体现通道的作用与优点。
以上全部内容只是个人的一点摸索,不代表完全正确。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Golang 学习笔记九 并发编程 协程 通道
- Golang并发:一招掌握无阻塞通道读写
- Go并发-使用协程、通道和select
- 科普 | 菜鸟学习状态通道,Part-2:App 定制型状态通道
- golang通道定义
- 通道 | Java NIO
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning XSLT 2.0
Jeni Tennison / Apress / 2005-07-22 / USD 49.99
This is an updated revision of Tennison's "Beginning XSLT", updated for the new revision of the XSLT standard. XSLT is a technology used to transform an XML document with one structure into another ......一起来看看 《Beginning XSLT 2.0》 这本书的介绍吧!