1 介绍
Go语言设计团队的首任负责人Rob Pike对并发编程的一个建议是不要让计算通过共享内存来通讯,而应该让它们通过通讯来共享内存。 通道机制就是这种哲学的一个设计结果。(在Go编程中,我们可以认为一个计算就是一个协程。)
通过共享内存来通讯和通过通讯来共享内存是并发编程中的两种编程风格。 当通过共享内存来通讯的时候,我们需要一些传统的并发同步技术(比如互斥锁)来避免数据竞争。
Go提供了一种独特的并发同步技术来实现通过通讯来共享内存。此技术即为通道。 我们可以把一个通道看作是在一个程序内部的一个先进先出(FIFO:first in first out)数据队列。 一些协程可以向此通道发送数据,另外一些协程可以从此通道接收数据。
随着一个数据值的传递(发送和接收),一些数据值的所有权从一个协程转移到了另一个协程。 当一个协程发送一个值到一个通道,我们可以认为此协程释放了(通过此发送值可以访问到的)一些值的所有权。 当一个协程从一个通道接收到一个值,我们可以认为此协程获取了(通过此接受值可以访问到的)一些值的所有权。
当然,在通过通道传递数据的时候,也可能没有任何所有权发生转移。
所有权发生转移的值常常被传递的值所引用着,但有时候也并非如此。 在Go中,数据所有权的转移并非体现在语法上,而是体现在逻辑上。 Go通道可以帮助程序员轻松地避免数据竞争,但不会防止程序员因为犯错而写出错误的并发代码的情况发生。
syncsync/atomic实事求是地说,每种并发同步技术都有它们各自的最佳应用场景,但是通道的应用范围更广。 使用通道来做同步常常可以使得代码看上去更整洁和易于理解。
通道的一个问题是通道的编程体验常常很有趣以至于程序员们经常在并非是通道的最佳应用场景中仍坚持使用通道。
2 通道类型和值
和数组、切片以及映射类型一样,每个通道类型也有一个元素类型。 一个通道只能传送它的(通道类型的)元素类型的值。
通道可以是双向的,也可以是单向的。
chan TTchan<- TT<-chan TTchan Tchan<- T<-chan Tchan<- T<-chan T每个通道值有一个容量属性。此属性的意义将在下一节中得到解释。 一个容量为0的通道值称为一个非缓冲通道(unbuffered channel),一个容量不为0的通道值称为一个缓冲通道(buffered channel)。
nilmakemake(chan int, 10)int03 一些通道的使用例子
来看一些通道的使用例子来加深一下对上一节中的解释的理解。
一个简单的通过一个非缓冲通道实现的请求/响应的例子:
输出结果:
下面的例子使用了一个缓冲通道。此例子程序并非是一个并发程序,它只是为了展示缓冲通道的使用。
一场永不休场的足球比赛:
4 数据接收和发送操作都属于简单语句
数据接收和发送操作都属于简单语句。 另外一个数据接收操作总是可以被用做一个单值表达式。 简单语句和表达式可以被用在一些控制流程的某些部分。
forfor-rangefor-rangefor-rangefor-range等价于
aChannelfor-rangefor为了让解释简单清楚,在本文后续部分,通道将被归为三类:
- 零值(nil)通道;
- 非零值但已关闭的通道;
- 非零值并且尚未关闭的通道。
下表简单地描述了三种通道操作施加到三类通道的结果。
| 操作 | 一个零值nil通道 | 一个非零值但已关闭的通道 | 一个非零值且尚未关闭的通道 |
|---|---|---|---|
| 关闭 | 产生恐慌 | 产生恐慌 | 成功关闭(C) |
| 发送数据 | 永久阻塞 | 产生恐慌 | 阻塞或者成功发送(B) |
| 接收数据 | 永久阻塞 | 永不阻塞(D) | 阻塞或者成功接收(A) |