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

学习Golang差很少半年了,go中的并发,通道,通道同步单个来说都不陌生,可是结合在一块儿运用的时候就有些懵逼,同时也不知道为什么要这么作。我想这是初学者都会遇到的困惑,在这里讲下本身的理解。缓存

为何用通道而不是共享变量

看一段代码bash

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个缓冲的通道。这时每一个协程各自处理本身的运算结果互不干扰,只在最后将各自运算结果写入到通道中。主线程再遍历通道进行读操做,只有当协程中有数据被写入时才能读取到数据而且汇总结果。因为读操做是在主线程中会发生阻塞,因此此时能够去掉睡眠,程序依然能正确执行,这就是通道同步。spa

若是通道读操做也开一个协程来处理会怎么样

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
复制代码

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

协程与主线程共享变量

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

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
复制代码

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

以上所有内容只是我的的一点摸索,不表明彻底正确。

相关文章
相关标签/搜索