Goroutines和Channels(五)

Channels也能够用于将多个goroutine链接在一块儿,一个Channel的输出做为下一个Channel的输入。这种串联的Channels就是所谓的管道(pipeline)。下面的程序用两个channels将三个goroutine串联起来:函数

第一个goroutine是一个计数器,用于生成0、一、二、……形式的整数序列,而后经过channel将该整数序列发送给第二个goroutine;第二个goroutine是一个求平方的程序,对收到的每一个整数求平方,而后将平方后的结果经过第二个channel发送给第三个goroutine;第三个goroutine是一个打印程序,打印收到的每一个整数。为了保持例子清晰,咱们有意选择了很是简单的函数,固然三个goroutine的计算很简单,在现实中确实没有必要为如此简单的运算构建三个goroutine。测试

 

func main() {
    naturals := make(chan int)
    squares := make(chan int)

    // Counter
    go func() {
        for x := 0; ; x++ {
            naturals <- x
        }
    }()

    // Squarer
    go func() {
        for {
            x := <-naturals
            squares <- x * x
        }
    }()

    // Printer (in main goroutine)
    for {
        fmt.Println(<-squares)
    }
}

  

如您所料,上面的程序将生成0、一、四、九、……形式的无穷数列。像这样的串联Channels的管道(Pipelines)能够用在须要长时间运行的服务中,每一个长时间运行的goroutine可能会包含一个死循环,在不一样goroutine的死循环内部使用串联的Channels来通讯。可是,若是咱们但愿经过Channels只发送有限的数列该如何处理呢?blog

若是发送者知道,没有更多的值须要发送到channel的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,由于接收者能够中止没必要要的接收等待。这能够经过内置的close函数来关闭channel实现:ip

 

close(naturals)

  

当一个channel被关闭后,再向该channel发送数据将致使panic异常。当一个被关闭的channel中已经发送的数据都被成功接收后,后续的接收操做将再也不阻塞,它们会当即返回一个零值。pip

关闭上面例子中的naturals变量对应的channel并不能终止循环,它依然会收到一个永无休止的零值序列,而后将它们发送给打印者goroutine。io

 

没有办法直接测试一个channel是否被关闭,可是接收操做有一个变体形式:它多接收一个结果,多接收的第二个结果是一个布尔值ok,ture表示成功从channels接收到值,false表示channels已经被关闭而且里面没有值可接收。使用这个特性,咱们能够修改squarer函数中的循环代码,当naturals对应的channel被关闭并无值可接收时跳出循环,而且也关闭squares对应的channel.class

 

// Squarer
go func() {
    for {
        x, ok := <-naturals
        if !ok {
            break // channel was closed and drained
        }
        squares <- x * x
    }
    close(squares)
}()

  

由于上面的语法是笨拙的,并且这种处理模式很常见,所以Go语言的range循环可直接在channels上面迭代。使用range循环是上面处理模式的简洁语法,它依次从channel接收数据,当channel被关闭而且没有值可接收时跳出循环。变量

在下面的改进中,咱们的计数器goroutine只生成100个含数字的序列,而后关闭naturals对应的channel,这将致使计算平方数的squarer对应的goroutine能够正常终止循环并关闭squares对应的channel。(在一个更复杂的程序中,能够经过defer语句关闭对应的channel。)最后,主goroutine也能够正常终止循环并退出程序。sed

 

func main() {
    naturals := make(chan int)
    squares := make(chan int)

    // Counter
    go func() {
        for x := 0; x < 100; x++ {
            naturals <- x
        }
        close(naturals)
    }()

    // Squarer
    go func() {
        for x := range naturals {
            squares <- x * x
        }
        close(squares)
    }()

    // Printer (in main goroutine)
    for x := range squares {
        fmt.Println(x)
    }
}

  

其实你并不须要关闭每个channel。只有当须要告诉接收者goroutine,全部的数据已经所有发送时才须要关闭channel。无论一个channel是否被关闭,当它没有被引用时将会被Go语言的垃圾自动回收器回收。(不要将关闭一个打开文件的操做和关闭一个channel操做混淆。对于每一个打开的文件,都须要在不使用的时候调用对应的Close方法来关闭文件。)循环

试图重复关闭一个channel将致使panic异常,试图关闭一个nil值的channel也将致使panic异常。关闭一个channels还会触发一个广播机制。

相关文章
相关标签/搜索