原文:https://golangbot.com/channels/
欢迎来到Golang 系列教程的第 22 章。
在上一教程里,我们探讨了如何使用 Go 协程(Goroutine)来实现并发。我们接着在本教程里学习信道(Channel),学习如何通过信道来实现 Go 协程间的通信。
什么是信道?
信道可以想像成 Go 协程之间通信的管道。如同管道中的水会从一端流到另一端,通过使用信道,数据也可以从一端发送,在另一端接收。
信道的声明
所有信道都关联了一个类型。信道只能运输这种类型的数据,而运输其他类型的数据都是非法的。
chan T
表示 T
类型的信道。
信道的零值为 nil
。信道的零值没有什么用,应该像对 map 和切片所做的那样,用 make
来定义信道。
下面编写代码,声明一个信道。
package main
import "fmt"
func main() {
var a chan int
if a == nil {
fmt.Println("channel a is nil, going to define it")
a = make(chan int)
fmt.Printf("Type of a is %T", a)
}
}
由于信道的零值为 nil
,在第 6 行,信道 a
的值就是 nil
。于是,程序执行了 if 语句内的语句,定义了信道 a
。程序中 a
是一个 int 类型的信道。该程序会输出:
channel a is nil, going to define it
Type of a is chan int
简短声明通常也是一种定义信道的简洁有效的方法。
a := make(chan int)
这一行代码同样定义了一个 int 类型的信道 a
。
通过信道进行发送和接收
如下所示,该语法通过信道发送和接收数据。
data := <- a // 读取信道 a
a <- data // 写入信道 a
信道旁的箭头方向指定了是发送数据还是接收数据。
在第一行,箭头对于 a
来说是向外指的,因此我们读取了信道 a
的值,并把该值存储到变量 data
。
在第二行,箭头指向了 a
,因此我们在把数据写入信道 a
。
发送与接收默认是阻塞的
发送与接收默认是阻塞的。这是什么意思?当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它 Go 协程从信道读取到数据,才会解除阻塞。与此类似,当读取信道的数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。
信道的这种特性能够帮助 Go 协程之间进行高效的通信,不需要用到其他编程语言常见的显式锁或条件变量。
信道的代码示例
理论已经够了:)。接下来写点代码,看看协程之间通过信道是怎么通信的吧。
我们其实可以重写上章学习Go协程 时写的程序,现在我们在这里用上信道。
首先引用前面教程里的程序。
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
这是上一篇的代码。我们使用到了休眠,使 Go 主协程等待 hello 协程结束。如果你看不懂,建议你阅读上一教程Go 协程。
我们接下来使用信道来重写上面代码。
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("Hello world goroutine")
d