Go信道
什么是信道
信道可以想象成Go协程之间通信的管道。通过使用信道,一端发送数据,一端则接受数据。
信道的声明
最简单的方式:
c := make(chan T)
其中,T为数据类型,信道只允许同种类型的数据传输。
信道收发数据
- 写数据
c <- data
- 读数据
data = <- c
信道旁的箭头方向指定了是发送数据还是接收数据。
在写数据时,箭头指向了 c,因此我们在把数据写入信道 c。
在读数据时,箭头对于 c 来说是向外指的,因此我们读取了信道 c 的值,并把该值存储到变量 data。
发送与接受数据默认是阻塞的。当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,知道有其它Go协程从信道读取数据,才会接触阻塞。与此类似,当读取信道数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。
信道的这种特性能够帮助Go协程之间进行高效的通信,不需要用到其他编程语言常见的显示锁或者条件变量。
代码示例:
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")
}
在上述程序里,我们在第 12 行创建了一个 bool 类型的信道 done,并把 done 作为参数传递给了 hello 协程。在第 14 行,我们通过信道 done 接收数据。这一行代码发生了阻塞,除非有协程向 done 写入数据,否则程序不会跳到下一行代码。于是,这就不需要用以前的 time.Sleep 来阻止 Go 主协程退出了。
<- done 为接受数据。这样Go主协程就发生了阻塞,等待信道done发送的数据。该信道作为参数传递给了协程hello,hello打印出 Hello world goroutine,接下来向 done 写入数据。当完成写入时,Go 主协程会通过信道 done 接收数据,于是它解除阻塞状态,打印出文本 main function。
死锁
使用信道需要考虑的一个重点是死锁。当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。
同理,当有 Go 协程等着从一个信道接收数据时,我们期望其他的 Go 协程会向该信道写入数据,要不然程序就会触发 panic。
所以说,接受有一个协程往信道写数据,必然需要一个协程从信道中读数据,两者是成对出现的。
单向信道和信道转换
信道还可以定义成,只有发送或者只有接受功能的单向信道。例如:
sendch := make(chan<- int)
关闭信道和使用for range遍历信道
信道关闭使用close(chan)。并且可以通过
v, ok := <- ch
判断信道是否关闭,ok为false时,表示信道已关闭。代码示例:
package main
import (
"fmt"
)
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}
在上述的程序中,producer 协程会从 0 到 9 写入信道 chn1,然后关闭该信道。主函数有一个无限的 for 循环(第 16 行),使用变量 ok(第 18 行)检查信道是否已经关闭。如果 ok 等于 false,说明信道已经关闭,于是退出 for 循环。如果 ok 等于 true,会打印出接收到的值和 ok 的值。