如果说Go有什么让人一见钟情的特性,那大概就是并行计算了吧。

做个题目

如果我们列出10以下所有能够被3或者5整除的自然数,那么我们得到的是3,5,6和9。这四个数的和是23。
那么请计算1000以下(不包括1000)的所有能够被3或者5整除的自然数的和。

这个题目的一个思路就是:

(1) 先计算1000以下所有能够被3整除的整数的和A,
(2) 然后计算1000以下所有能够被5整除的整数和B,
(3) 然后再计算1000以下所有能够被3和5整除的整数和C,
(4) 使用A+B-C就得到了最后的结果。

按照上面的方法,传统的方法当然就是一步一步计算,然后再到第(4)步汇总了。

但是一旦有了Go,我们就可以让前面三个步骤并行计算,然后再在第(4)步汇总。

数据类型chan关键字go

先看例子:

最后一个参数是一个整型chan类型向它写入数据从它读出数据所能接受的数据类型由chan关键字后面的类型所决定<-
goroutine协程go关键字跟上所要运行的函数
<-

在本例中,我们为了演示go并行计算的速度,还引进了time包来计算程序执行时间。在同普通的顺序计算相比,并行计算的速度是非同凡响的。

好了,上面的例子看完,我们来详细讲解Go的并行计算。

Go Routine 协程

所谓协程,就是Go提供的轻量级的独立运算过程,比线程还轻。创建一个协程很简单,就是go关键字加上所要运行的函数。看个例子:

发现什么都没有输出为什么呢?main函数创建完协程后立刻退出协程没有来得及运行

这里,我们在main函数创建协程后,要求用户输入任何数据后才退出,这样协程就有了运行的时间,故而输出结果:

sum3, sum5, sum15 := <-resultChan, <-resultChan, <-resultChan

不过既然是并行计算,我们还是得看看协程是否真的并行计算了。

输出结果

在上面的例子中,我们让两个协程在每输出一个数字的时候,随机Sleep了一会儿。如果是并行计算,那么输出是无序的。从上面的例子中,我们可以看出两个协程确实并行运行了。

Channel通道
协程之间通信方式运行同步机制

假设训练定点投篮和三分投篮,教练在计数。

输出结果为:

go count(c)如果你要向channel里面写信息必须有配对的取信息的一端

我们再把三分投篮加上。

输出结果为:

我们看到程序交替输出定点投篮和三分投篮,这是因为写入channel的信息必须要读取出来,否则尝试再次写入就失败了。

定义一个channel信息变量chan向channel写入数据从channel读取数据
Channel通道方向*
通道方向写读
c chan<- string //那么你只能向channel写入数据

而这种定义

c <-chan string //那么你只能从channel读取数据
试图向只读chan变量写入数据或者试图从只写chan变量读取数据都会导致编译错误。

如果是默认的定义方式

c chan string //那么你既可以向channel写入数据也可以从channnel读取数据
多通道(Select)

如果上面的投篮训练现在有两个教练了,各自负责一个训练项目。而且还在不同的篮球场,这个时候很显然,我们一个channel就不够用了。修改一下:

其他的和上面的一样,唯一不同的是我们将定点投篮和三分投篮的消息写入了不同的channel,那么main函数如何知道从哪个channel读取消息呢?使用select方法,select方法依次检查每个channel是否有消息传递过来,如果有就取出来输出。如果同时有多个消息到达,那么select闭上眼睛随机选一个channel来从中读取消息,如果没有一个channel有消息到达,那么select语句就阻塞在这里一直等待。

在某些情况下,比如学生投篮中受伤了,那么就轮到医护人员上场了,教练在一般看看,如果是重伤,教练就不等了,就回去了休息了,待会儿再过来看看情况。我们可以给select加上一个case用来判断是否等待各个消息到达超时。

<-time.After(time.Second*5)

输出结果如下:

default的选项没有消息到达也不会阻塞
Channel Buffer通道缓冲区
缓冲区大小定义的channel都是同步的指定了一个缓冲区消息的发送和接受式异步的除非channel缓冲区已经满了

我们看个例子:

输出结果为:

你可以尝试运行一下,每次都是一下子输出10个数据。然后等待10秒再输出一批。

小结

并行计算这种特点最适合用来开发网站服务器,因为一般网站服务都是高并发的,逻辑十分复杂。而使用Go的这种特性恰是提供了一种极好的方法。