Golang 并发,有缓存通道,通道同步案例演示

栏目: Go · 发布时间: 6年前

内容简介:学习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
复制代码

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

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Mechanics of Web Handling

The Mechanics of Web Handling

David R. Roisum

This unique book covers many aspects of web handling for manufacturing, converting, and printing. The book is applicable to any web including paper, film, foil, nonwovens, and textiles. The Mech......一起来看看 《The Mechanics of Web Handling》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具