Goroutines和Channels(五)
Channels也可以用於將多個goroutine連接在一起,一個Channel的輸出作為下一個Channel的輸入。這種串聯的Channels就是所謂的管道(pipeline)。下面的程序用兩個channels將三個goroutine串聯起來:
第一個goroutine是一個計數器,用於生成0、1、2、……形式的整數序列,然後通過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、1、4、9、……形式的無窮數列。像這樣的串聯Channels的管道(Pipelines)可以用在需要長時間運行的服務中,每個長時間運行的goroutine可能會包含一個死循環,在不同goroutine的死循環內部使用串聯的Channels來通信。但是,如果我們希望通過Channels只發送有限的數列該如何處理呢?
如果發送者知道,沒有更多的值需要發送到channel的話,那麽讓接收者也能及時知道沒有多余的值可接收將是有用的,因為接收者可以停止不必要的接收等待。這可以通過內置的close函數來關閉channel實現:
close(naturals)
當一個channel被關閉後,再向該channel發送數據將導致panic異常。當一個被關閉的channel中已經發送的數據都被成功接收後,後續的接收操作將不再阻塞,它們會立即返回一個零值。
關閉上面例子中的naturals變量對應的channel並不能終止循環,它依然會收到一個永無休止的零值序列,然後將它們發送給打印者goroutine。
沒有辦法直接測試一個channel是否被關閉,但是接收操作有一個變體形式:它多接收一個結果,多接收的第二個結果是一個布爾值ok,ture表示成功從channels接收到值,false表示channels已經被關閉並且裏面沒有值可接收。使用這個特性,我們可以修改squarer函數中的循環代碼,當naturals對應的channel被關閉並沒有值可接收時跳出循環,並且也關閉squares對應的channel.
// 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也可以正常終止循環並退出程序。
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還會觸發一個廣播機制。
Goroutines和Channels(五)