区块链技术语言(二十七)——Go语言并发编程(上)

并发编程分为上、下两节。这一节包括了并发编程的概述、goroutine和channel的部分内容。

一、概述

1.1

并行和并发并行(parallel):在多个处理器上同时执行多条指令,如图1所示。

并发(concurrency):同一时刻只有一条指令在执行,但多个进程指令被快速轮换地执行,使得宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的。它只是把这段时间进行划分、快速交替地执行,如图2所示。

1.2 Go语言并发优势

Go语言原生支持并发。首先,虽然一个并发程序内存管理复杂,但Go语言提供了自动垃圾回收机制,可以自动判断何时需要释放内存,并在CPU相对空闲的时候对不使用的内存进行收集。其次,Go语言提供了goroutine。goroutine即一个函数或方法,它仅需4~5KB的内存,所以创建一个goroutine代价极小。另外,Go语言存在一个内置的数据结构——通道(channel),它能够让不同的goroutine之间同步安全地发送消息。因此,Go语言让并发编程变得更加轻盈和安全。因此,一个运行的程序可以创建成百上千个goroutine,充分利用计算机资源,自动管理内存,实现了高并发。

二、goroutine

2.1 goroutine的定义

关于goroutine,Go语言之父Rob Pike说:“一个goroutine是一个与其它goroutines并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同,它是一个goroutine。”所以我们可以认为goroutine就是一个函数或方法。

2.2 goroutine的创建和运行

在函数或者方法的调用语句之前添加关键字go,就可以创建一个goroutine。开发人员无需了解任何执行细节,调度器会自动将其安排到合适的系统线程上执行。当一个程序启动时,其主函数在一个单独的goroutine中运行,这个goroutine叫作main goroutine。新的goroutine用go语句来创建。

2.2.1 main goroutine

在Go语言里,主函数运行在main goroutine中,其它goroutines和main goroutine并发运行。如果main goroutine先执行完毕,那么其它的goroutines也会自动退出。

2.2.2 其它goroutines

如果要运行所有其它的goroutines,main goroutine必须继续在运行,直到其它goroutines运行完毕。

2.3 runtime包

2.3.1 Gosched

runtime.Gosched() 用于让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

2.3.2 Goexit

调用runtime.Goexit(),会立即终止当前goroutine的执行,调度器确保所有已注册defer延迟调用被执行。

2.3.3 GOMAXPROCS

调用runtime.GOMAXPROCS() 用来设置可以并行计算的CPU核数的最大值,并且返回设置之前用于并行计算的CPU核数最大值。

三、channel

在Go语言里,各个goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。当一个资源需要在两个goroutine之间共享,channel在两个goroutine之间架起一个管道,并提供了确保同步交换数据的机制。可以通过channel共享内置类型、命名类型、结构类型和引用类型的值或指针。channel是基于底层数据结构的引用,当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用。和其它的引用类型一样,channel的零值也是nil。

3.1 channel的创建

在Go语言中,通过内置函数make可以创建一个channel,需要定义发送到channel的值的类型。其创建格式如下:

注:a. 当capacity=0时,channel是无缓冲阻塞读写的;
b. 当capacity>0时,channel有缓冲、非阻塞的,直到写满capacity个元素才阻塞写入。3.2 channel中的数据的发送和接收

channel通过操作符<-来接收和发送数据,发送和接收数据的语法格式如表1所示。默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好。

3.3 无缓冲的channel

3.3.1 概述

无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。这种类型的通道要求同时准备好发送goroutine和接收goroutine,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个操作都无法离开另一个操作单独存在。图1通过示意图分析了两个goroutine利用无缓冲通道共享一个值:
a. 第1步,两个goroutine都到达通道两端,但两个都没有开始执行发送数据或者接收数据;
b. 第2步,左侧的goroutine将它的手伸进通道,这模拟了向通道发送数据的行为。此时,这个goroutine会在通道中被锁住,直到交换完成;
c. 第3步,右侧的goroutine将它的手放入通道,这模拟了从通道里接收数据。这个goroutine也一样会在通道中被锁住,直到交换完成;
d. 第4步和第5步,进行数据交换;
e. 第6步,两个goroutine都将它们的手从通道里拿出来,这模拟了被锁住的goroutine得到释放。两个goroutine现在都可以去做别的事情了。

3.3.2 无缓冲channel的创建

无缓冲channel没有指定缓冲区容量,那么在无缓冲的channel中,数据发送和接收同步,其创建格式如下:

示例如下:

3.3.3 内置函数close在channel中的应用

如果发送者知道没有更多的值发送到channel,那么让接收者也能及时知道没有多余的值可接收将是有用的。因为接收者可以停止不必要的接收等待,这可以通过内置的close函数来实现channel的关闭。channel不像文件一样需要经常关闭,只有确实没有任何数据发送到channel,才关闭channel;关闭channel后,无法向channel再发送数据;对于nil channel,无论收发都会被阻塞。

3.3.4 用range接收channel中的数据

关键词range结合for循环可用于在一个channel关闭之前,从channel中接收数据。如果要结束此操作,用close函数关闭channel。


参考资料:


posted @ 2019-03-07 15:40  网鱼  阅读(242)  评论(0编辑  收藏  举报