一段小程序浅析Go中的并发,协程(goroutine),sync.WaitGroup

package main

import (
	"fmt"
	"runtime"
	"sync"
)

func main() {
	runtime.GOMAXPROCS(2)
	fmt.Println("begin typing")
	var wg sync.WaitGroup
	//defer wg.Done()
	wg.Add(2)
	go func() {
		defer wg.Done()
		for i := 'A'; i < 'A'+26; i++ {
			fmt.Printf("%c ", i)
		}
		fmt.Println()
	}()
	go func() {
		defer wg.Done()
		for i := 'a'; i < 'a'+26; i++ {
			fmt.Printf("%c ", i)
		}
		fmt.Println()
	}()
	wg.Wait()
	fmt.Println("typing finished")
}

运行结果:

      本程序的功能是为了打印26个英文字母,先打印大写字母,再打印小写字母。

      在书写本程序的过程中,遇到了几个坑,接下来依次说明,对于理解题目中说的那些概念应该挺有帮助。

1,并发与并行

并发的意思可以理解成是时分复用,对于CPU来说,有一条时间总线,很多任务的并发实际上是这些任务执行一段时间就必须暂停,让其它任务去执行。本质上还是顺序执行的,只是说,采用了轮询的方式,加上CPU的处理速度非常快,所以,制造了一个好像大家是齐头并进的假象。

并行是针对多核CPU而言的,每一个核都有一条总线,它们互相之间彼此独立地执行任务,互不影响。

程序中有一句runtime.GOMAXPROCS(),括号里面跟的数字就是希望几个核来完成这个程序,如果你不知道几个核的话,还可以使用自动检测CPU的核的方法runtime.NumCPU。

我的开发机的物理配置是单核CPU,因此,即使我设置了CPU的核数等于2,也没用!以下程序的结果可以说明问题。如果是双核的CPU,那么输出的结果,应该向以下这样:

A B C a D E b F c G d H e I f J g K h L i M j N k O l P m Q n R o S p T q U r V s W t X u Y v Z w

如何查看自己的linux机器是几核的?命令:cat /proc/cpuinfo| grep "cpu cores"| uniq       输出结果:cpu cores : 1

2,为什么要用waitgroup?

如果程序同时开启两个协程(goroutine),它们之间是一个并发关系,互相之间也没有什么影响,但是,main函数并不会去等待这两个协程执行完毕,它会一直往下走,程序往下走的速度比协程的执行速度快,因此,这个时候,运行这个程序并不会有任何的输出,这是一个非常神奇的事情。与此同时,两个协程运行的速度也是有差异的,这也就是为什么程序里面明明打印大写字母的协程在前面,结果里面却是小写字母在前面。

WaitGroup里面有一些子方法可以很好地管理这些协程。比如Add,就是告诉程序,现在有几个协程工作,Done出现一次,就会让Add后面的那个数减一,而Wait就是想等到Add里面的数为0为止。defer这个函数有点类似Python处理异常时的finally,就是一定会执行的语句,且该语句会在return之前执行。

所以,以上程序的意思就是,首先说明,接下来会启动两个协程,然后程序就开启了两个协程,每一个协程执行完毕,就会减一,Wait会等到减到0的时候,再进行下面的操作。

3,为什么结果不会交叉随机出现?

因为CPU的处理太快了,所以,在给每一个协程分配的时间片内,就足够完成整个协程的功能了,如果给每一个Print加上一个Sleep,那么,一个时间片内完不成任务,就要开始轮询模式了,输出什么东西就随机了。改动如下:

输出结果:

4,如何实现交叉出现呢?-runtime.Gosched()函数

使用runtime.Gosched()函数,可以使得当前的goroutine从线程中退出,让其它的线程执行,因此,如果在每一句Print后面紧接着跟一句Gosched(),就会得到如下结果。该命令可以实现协程线程之间的切换。

5,为什么先加载小写开始执行呢?

对于平行关系的协程,操作系统先加载哪一个运行哪一个这个说不准,所以写并发的代码,不必纠结于这个问题。

猜你喜欢

转载自blog.csdn.net/Bubbler_726/article/details/82688437