Channel
下面的几个例子将会展示如何定义一个 channel:
func chanDemo() {
var c chan int // chan int 的含义是, c 是一个 channel, 里面的内容是 int
// 上面的声明语句将会创建一个 nil channel, c == nil, 它的作用将在 select 当
// 中体现
}
创建一个非 nil 的 channel 的方法是使用内建的 make 函数:
package main
import "fmt"
func chanDemo() {
c := make(chan int)
go func() { // 由于 channel 是 goroutine 之间的通道
for { // 因此应该建立一个 goroutine 不断收数据
n := <-c // 从 channel 收数据
fmt.Println(n)
}
}()
c <- 1 // 向 channel 发数据
c <- 2 // 向 channel 发数据
}
func main() {
chanDemo()
}
Channel 也是 Golang 当中的一等公民
在函数式编程的学习过程中,我们已经看到,函数是 Golang 的一等公民,它可以作为参数,也可以作为返回值。而 Channel 同样也是 Golang 的一等公民,它同样可以作为参数也可以作为返回值。
为了展示其用法,我们对上面的代码进行修改,首先将上面代码当中的匿名函数单独定义为一个函数:
package main
import (
"fmt"
"time"
)
func worker(c chan int) { // channel 作为参数
for {
n := <-c // 从 channel 收数据
fmt.Println(n)
}
}
func chanDemo() {
c := make(chan int)
go worker(c)
c <- 1 // 向 channel 发数据
c <- 2 // 向 channel 发数据
time.Sleep(time.Second)
}
func main() {
chanDemo()
}
其输出仍然是 1 和 2。
为了进一步体现 channel 是一等公民,使用 var 声明一个存储 10 个 chan int 型 channel 的数组,并在循环中使用内建的 make 对每个 chan int 进行构造,并将 chan int 分发给 10 个 workers:
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int) { // channel 作为参数
for {
n := <-c // 从 channel 收数据
fmt.Printf("Worker %d received %c\n", id, n)
}
}
const worker_num int = 10
func chanDemo() {
var channels [10]chan int // 建立一个包含 10 个 chan int 的数组 channels
for i := range worker_num {
channels[i] = make(chan int) // 使用内建的 make 构造每一个 channel
go worker(i, channels[i]) // 将 10 个 chan int 分发给 10 个 worker
}
for i := range worker_num {
channels[i] <- 'a' + i
}
time.Sleep(time.Millisecond)
}
func main() {
chanDemo()
}
输出的结果如下,因操作设备而异:
Worker 0 received a
Worker 2 received c
Worker 1 received b
Worker 3 received d
Worker 9 received j
Worker 4 received e
Worker 5 received f
Worker 6 received g
Worker 7 received h
Worker 8 received i
channel 除了可以作为参数、可以存储在数组当中,channel 还可以作为返回值:
package main
import (
"fmt"
"time"
)
func createWorker(id int) chan<- int { // 返回值 chan int 用于发数据, 因此添加 <- 告知使用者
// 实际上这个函数的执行相当快: 新建一个 chan int, 并开启一个由匿名函数构成的 goroutine, 之后就返回建立的 chan int
// goroutine 将会一直运行下去
c := make(chan int) // 在函数中创建 channel, 它也是返回值
go func() { // 开一个 goroutine, 来接受 channel 得到的内容
for {
n := <-c // 从 channel 收数据
fmt.Printf("Worker %d received %c\n", id, n)
}
}()
return c
}
const worker_num int = 10
func chanDemo() {
var channels [10]chan<- int // 建立一个包含 10 个 chan int 的数组 channels
for i := range worker_num {
channels[i] = createWorker(i)
}
for i := range worker_num {
channels[i] <- 'a' + i
}
for i := range worker_num {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}
func main() {
chanDemo()
}
输出的结果为:
Worker 0 received a
Worker 0 received A
Worker 4 received e
Worker 9 received j
Worker 6 received g
Worker 7 received h
Worker 8 received i
Worker 2 received c
Worker 1 received b
Worker 1 received B
Worker 5 received f
Worker 3 received d
Worker 3 received D
Worker 2 received C
Worker 6 received G
Worker 4 received E
Worker 5 received F
Worker 7 received H
Worker 8 received I
Worker 9 received J
bufferedChannel
之前我们提到过,建立一个 channel 之后,如果向 channel 当中写入数据,则应该有一个 goroutine 负责接收数据(即,在 go func 的函数体内进行 n := <- channel
)。
比如,运行下述代码:
func bufferedChannel() {
c := make(chan int)
c <- 1
}
func main() {
bufferedChannel()
}
将会产生如下的错误信息:
可以为 channel 加入一个缓冲区,来解决上述问题:
func bufferedChannel() {
c := make(chan int, 3) // 在 make 当中进一步输入参数 3, 代表当前缓冲区的大小设置为 3
c <- 1
c <- 2
c <- 3
}
func main() {
bufferedChannel()
}
此时程序可以正常运行,不会 deadlock,如果进一步发送 4,才会 deadlock。
现在输出 channel 当中的数据:
func worker(id int, c chan int) {
for {
n := <-c // 从 channel 收数据
fmt.Printf("Worker %d received %d\n", id, n)
}
}
func bufferedChannel() {
c := make(chan int, 3) // 在 make 当中进一步输入参数 3, 代表当前缓冲区的大小设置为 3
go worker(0, c)
c <- 1
c <- 2
c <- 3
c <- 4
time.Sleep(time.Millisecond)
}
func main() {
bufferedChannel()
}
输出的结果为:
Worker 0 received 1
Worker 0 received 2
Worker 0 received 3
Worker 0 received 4
我们还想要知道,什么时候 channel 当中的数据发完了。应该由发送方来通知接收方,没有新的数据要发送了:
func channelClose() {
c := make(chan int)
go worker(0, c)
c <- 1
c <- 2
c <- 3
c <- 4
close(c) // 告诉接受方, 数据发完了
time.Sleep(time.Millisecond)
}
func main() {
channelClose()
}
输出的结果如下:
Worker 0 received 1
Worker 0 received 2
Worker 0 received 3
Worker 0 received 4
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
... (Worker 0 received 0 repeated ...)
当 channel 通过 close 通知 goroutine 没有数据要发送了的时候,goroutine 仍然会接收数据,只不过接受的数据是零值。在 goroutine 接收 channel 的数据时,可以使用两个返回值 n 和 ok 来判断 channel 是否 close,因此我们对 worker 进行修改:
func worker(id int, c chan int) {
for {
n, ok := <-c // 使用两个值来判断 channel 是否 close
if !ok {
break
}
fmt.Printf("Worker %d received %d\n", id, n)
}
}
此时的输出为:
Worker 0 received 1
Worker 0 received 2
Worker 0 received 3
Worker 0 received 4
另一种判断 channel 是否 close 的方式是在 for 循环使用:
func worker(id int, c chan int) {
for n := range c {
fmt.Printf("Worker %d received %d\n", id, n)
}
}
可以得到相同的结果。
总结上述学习到的内容:
- 本节介绍了 channel 及其定义、发送、接收数据的方法;
- 如何定义以及使用 bufferd channel,在 make 内置方法构造 channel 时显式地指定 buffer 的大小即可;
- 在接收数据时使用 range 可以方便地判断 channel 是否关闭。
为什么使用 Channel?原因是:不要通过共享内存来通信,而是通过通信来共享内存。
通过共享内存来通信的典型例子是,两个线程之间通过一个 flag 变量来判断事务是否完成。