《Go语言四十二章经》第二十二章 通道(channel) - 22.1 通道(channel)
- 22.1 通道(channel)
《Go语言四十二章经》第二十二章 通道(channel)
作者:李骁
22.1 通道(channel)
Go 奉行通过通信来共享内存,而不是共享内存来通信。所以,channel 是goroutine之间互相通信的通道,goroutine之间可以通过它发消息和接收消息。
channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。
channel是类型相关的,一个channel只能传递(发送或接受 | send or receive)一种类型的值,这个类型需要在声明channel时指定。
默认的,信道的存消息和取消息都是阻塞的 (叫做无缓冲的信道)
使用make来建立一个通道:
var channel chan int = make(chan int)// 或channel := make(chan int)
Go中channel可以是发送(send)、接收(receive)、同时发送(send)和接收(receive)。
// 定义接收的channelreceive_only := make (<-chan int)// 定义发送的channelsend_only := make (chan<- int)// 可同时发送接收send_receive := make (chan int)
- chan<- 表示数据进入通道,要把数据写进通道,对于调用者就是发送。
- <-chan 表示数据从通道出来,对于调用者就是得到通道的数据,当然就是接收。
定义只发送或只接收的channel意义不大,一般用于在参数传递中:
package mainimport ("fmt""time")func main() {c := make(chan int) // 不使用带缓冲区的channelgo send(c)go recv(c)time.Sleep(3 * time.Second)close(c)}// 只能向chan里send数据func send(c chan<- int) {for i := 0; i < 10; i++ {fmt.Println("send readey ", i)c <- ifmt.Println("send ", i)}}// 只能接收channel中的数据func recv(c <-chan int) {for i := range c {fmt.Println("received ", i)}}
程序输出:send readey 0send 0send readey 1received 0received 1send 1send readey 2send 2send readey 3received 2received 3send 3send readey 4send 4send readey 5received 4received 5send 5send readey 6send 6send readey 7received 6received 7send 7send readey 8send 8send readey 9received 8received 9send 9
运行结果上我们可以发现一个现象,往channel 发送数据后,这个数据如果没有取走,channel是阻塞的,也就是不能继续向channel 里面发送数据。因为上面代码中,我们没有指定channel 缓冲区的大小,默认是阻塞的。
我们可以建立带缓冲区的 channel:
c := make(chan int, 1024)
我们把前面的程序修改下:
package mainimport ("fmt""time")func main() {c := make(chan int, 10) // 使用带缓冲区的channelgo send(c)go recv(c)time.Sleep(3 * time.Second)close(c)}// 只能向chan里send发送数据func send(c chan<- int) {for i := 0; i < 10; i++ {fmt.Println("send readey ", i)c <- ifmt.Println("send ", i)}}// 只能接收channel中的数据func recv(c <-chan int) {for i := range c {fmt.Println("received ", i)}}
程序输出:send readey 0send 0send readey 1send 1send readey 2send 2send readey 3send 3send readey 4send 4send readey 5received 0received 1received 2received 3received 4received 5send 5send readey 6send 6send readey 7send 7send readey 8send 8send readey 9send 9received 6received 7received 8received 9
从运行结果我们可以看到(每次执行顺序不一定相同,goroutine 运行导致的原因),带有缓冲区的channel,在缓冲区有数据而未填满前,读取不会出现阻塞的情况。
- 无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。
- 有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。
这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。
如果给定了一个缓冲区容量,通道就是异步的。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。
可以通过内置的close函数来关闭channel实现。
channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
关闭channel后,可以继续向channel接收数据,不能继续发送数据;
对于nil channel,无论收发都会被阻塞。
