《Go语言实战》读书笔记,未完待续,欢迎扫码关注公众号
flysnow_org
,第一时间看后续笔记。觉得有帮助的话,顺手分享到朋友圈吧,感谢支持。
上一篇我们讲的原子函数和互斥锁,都可以保证共享数据的读写,但是呢,它们还是有点复杂,而且影响性能,对此,Go又为我们提供了一种工具,这就是通道。
所以在多个goroutine并发中,我们不仅可以通过原子函数和互斥锁保证对共享资源的安全访问,消除竞争的状态,还可以通过使用通道,在多个goroutine发送和接受共享的数据,达到数据同步的目的。
通道,他有点像在两个routine之间架设的管道,一个goroutine可以往这个管道里塞数据,另外一个可以从这个管道里取数据,有点类似于我们说的队列。
声明一个通道很简单,我们使用chan
关键字即可,除此之外,还要指定通道中发送和接收数据的类型,这样我们才能知道,要发送什么类型的数据给通道,也知道从这个通道里可以接收到什么类型的数据。
|
|
通道类型和Map这些类型一样,可以使用内置的make
函数声明初始化,这里我们初始化了一个chan int
类型的通道,所以我们只能往这个通道里发送int
类型的数据,当然接收也只能是int
类型的数据。
我们知道,通道是用于在goroutine之间通信的,它具有发送和接收两个操作,而且这两个操作的运算符都是<-
。
|
|
看例子,慢慢理解发送和接收的用法。发送操作<-
在通道的后面,看箭头方向,表示把数值2发送到通道ch
里;接收操作<-
在通道的前面,而且是一个一元操作符,看箭头方向,表示从通道ch
里读取数据。读取的数据可以赋值给一个变量,也可以忽略。
通道我们还可以使用内置的close
函数关闭。
|
|
如果一个通道被关闭了,我们就不能往这个通道里发送数据了,如果发送的话,会引起painc
异常。但是,我们还可以接收通道里的数据,如果通道里没有数据的话,接收的数据是nil
。
刚刚我们使用make
函数初始化的时候,只有一个参数,其实make
还可以有第二个参数,用于指定通道的大小。默认没有第二个参数的时候,通道的大小为0,这种通道也被成为无缓冲通道
。
|
|
看例子,其中第一个和第二个初始化是等价的。第三个初始化创建了一个大小为2的通道,这种称为有缓冲通道
。
无缓冲的通道
无缓冲的通道指的是通道的大小为0,也就是说,这种类型的通道在接收前没有能力保存任何值,它要求发送goroutine和接收goroutine同时准备好,才可以完成发送和接收操作。
从上面无缓冲的通道定义来看,发送goroutine和接收gouroutine必须是同步的,同时准备后,如果没有同时准备好的话,先执行的操作就会阻塞等待,直到另一个相对应的操作准备好为止。这种无缓冲的通道我们也称之为同步通道
。
|
|
在前面的例子中,我们为了演示goroutine,防止程序提前终止,都是使用sync.WaitGroup
进行等待,现在的这个例子就不用了,我们使用同步通道来等待。
在计算sum和的goroutine没有执行完,把值赋给ch
通道之前,fmt.Println(<-ch)
会一直等待,所以main
主goroutine就不会终止,只有当计算和的goroutine完成后,并且发送到ch
通道的操作准备好后,同时<-ch
就会接收计算好的值,然后打印出来。
管道
我们在使用Bash的时候,有个管道操作|
,它的意思是把上一个操作的输出,当成下一个操作的输入,连起来,做一连串的处理操作。
|
|
比如上面这个例子的意思是,先使用ls
命令,把当前目录下的目录和文件列出来,作为下一个grep
命令的输入,然后通过grep
命令,匹配我们需要显示的目录和文件,这里匹配以D
开头的文件名或者目录名。
其实我们使用通道也可以做到管道的效果,我们只需要把一个通道的输出,当成下一个通道的输入即可。
|
|
这里例子中我们定义两个通道one
和two
,然后按照顺序,先把100发送给通道one
,然后用另外一个goroutine从one
接收值,再发送给通道two
,最终在主goroutine里等着接收打印two
通道里的值,这就类似于一个管道的操作,把通道one
的输出,当成通道two
的输入,类似于接力赛一样。
有缓冲的通道
有缓冲通道,其实是一个队列,这个队列的最大容量就是我们使用make
函数创建通道时,通过第二个参数指定的。
|
|
这里创建容量为3的,有缓冲的通道。对于有缓冲的通道,向其发送操作就是向队列的尾部插入元素,接收操作则是从队列的头部删除元素,并返回这个刚刚删除的元素。
当队列满的时候,发送操作会阻塞;当队列空的时候,接受操作会阻塞。有缓冲的通道,不要求发送和接收操作时同步的,相反可以解耦发送和接收操作。
想知道通道的容量以及里面有几个元素数据怎么办?其实和map
一样,使用cap
和len
函数就可以了。
|
|
cap
函数返回通道的最大容量,len
函数返回现在通道里有几个元素。
|
|
这是Go语言圣经里比较有意义的一个例子,例子是想获取服务端的一个数据,不过这个数据在三个镜像站点上都存在,这三个镜像分散在不同的地理位置,而我们的目的又是想最快的获取到数据。
所以这里,我们定义了一个容量为3的通道responses
,然后同时发起3个并发goroutine向这三个镜像获取数据,获取到的数据发送到通道responses
中,最后我们使用return <-responses
返回获取到的第一个数据,也就是最快返回的那个镜像的数据。
单向通道
有时候,我们有一些特殊场景,比如限制一个通道只可以接收,但是不能发送;有时候限制一个通道只能发送,但是不能接收,这种通道我们称为单向通道。
定义单向通道也很简单,只需要在定义的时候,带上<-
即可。
|
|
注意<-
操作符的为止,在后面是只能发送,对应发送操作;在前面是只能接收,对应接收操作。
单向通道应用于函数或者方法的参数比较多,比如
|
|
例子这样的,只能进行发送操作,防止误操作,使用了接收操作,如果使用了接收操作,在编译的时候就会报错的。
使用通道可以很简单的在goroutine之间共享数据,下一篇会具体介绍一些例子,以便更好的理解并发。
《Go语言实战》读书笔记,未完待续,欢迎扫码关注公众号
flysnow_org
,第一时间看后续笔记。觉得有帮助的话,顺手分享到朋友圈吧,感谢支持。
有疑问加站长微信联系(非本文作者)