Go 语言那些事儿之管道的关闭
之前我们提到了怎么定义管道,讲了管道的读取和写入。那么今天我们就来讲一讲管道的关闭。
先来看一个简单的例子:
func main() {
ch := make(chan int,10)
for i := 0;i < 5;i++{
ch <- i*i
}
close(ch)
for x:= range ch {
fmt.Println(x)
}
}
复制代码
先思考一下它会输出什么?
没错它的输出结果是:
这段的代码就是定义一个管道,然后遍历零到四,将零到四的平方塞入管道。我们知道这个管道定义时,被赋予了 10 的缓存能力,所以能够缓存 10 个单位的数据。而我们这里只需要存 5 个单位,你会想是不是还有 5 个单位是空闲的?是的。
下面我们再看一看多个协程并发读写管道:
func main() {
ch := make(chan int,10)
go func() {
for i:=0;i<5;i++{
ch <- i * i
fmt.Println("写入",i*i)
time.Sleep(1 * time.Second)
}
// The close built-in function closes a channel, which must be either
// bidirectional or send-only. It should be executed only by the sender,
// never the receiver, and has the effect of shutting down the channel after
// the last sent value is received. After the last value has been received
// from a closed channel c, any receive from c will succeed without
// blocking, returning the zero value for the channel element. The form
// x, ok := <-c
// will also set ok to false for a closed channel.
close(ch)
fmt.Println("管道已关闭,写入协程结束")
}()
go func() {
for x:= range ch{
fmt.Println("读出",x)
}
fmt.Println("读取协程结束")
}()
time.Sleep(6 * time.Second)
fmt.Println("GAME OVER")
}
复制代码
运行结果是:
写入 0
读出 0
读出 1
写入 1
写入 4
读出 4
写入 9
读出 9
写入 16
读出 16
读取协程结束
管道已关闭,写入协程结束
GAME OVER
复制代码
这段代码的作用是建立一个缓存能力为 10 的管道,建立两条协程。一条协程用来将零到四的平方写入管道,写完之后关闭管道。另一条协程是用来将管道内的数据读出。
由于两条协程是并发的,所以一条协程往管道里写入一条数据,马上另一条协程就往管道里读取一条数据。这里那条写入数据的协程关闭了管道,那么就会通知那边不需要再继续从管道中读取数据了,那么另外一条协程就不会往管道里面读取数据了。如果没有close(ch)
,也就是不关闭管道,那么另一条协程就会不断读取管道,直到主协程正常结束,那条子协程才会结束。
我们需要注意,内建函数 close 函数是用来关闭通道的,并且只有双向的和仅发送的管道才能被关闭。
还有一点需要注意的是,这个 close 函数需要由发送方执行,而不是由接收方执行。从一个关闭的通道接收完最后一个值后,从该管道接收的任何值都将成功,不会阻塞,并返回零值。但是对于x, ok := <-c
,将会为关闭的管道的 ok 值赋予false
值。
评论