Go语言那些事儿之管道的关闭

1,597 阅读3分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

本文收录于我的专栏:《让我们一起Golang》

之前我们提到了怎么定义管道,讲了管道的读取和写入。那么今天我们就来讲一讲管道的关闭

先来看一个简单的例子:

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)
    }
}

先思考一下它会输出什么?

没错它的输出结果是:

0
1
4
9
16

这段的代码就是定义一个管道,然后遍历零到四,将零到四的平方塞入管道。我们知道这个管道定义时,被赋予了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值。