### 8.4.2. 串聯的Channels（Pipeline）

Channels也可以用於將多個goroutine鏈接在一起，一個Channels的輸出作爲下一個Channels的輸入。這種串聯的Channels就是所謂的管道（pipeline）。下面的程序用兩個channels將三個goroutine串聯起來，如圖8.1所示。

![](../images/ch8-01.png)

第一個goroutine是一個計數器，用於生成0、1、2、……形式的整數序列，然後通過channel將該整數序列發送給第二個goroutine；第二個goroutine是一個求平方的程序，對收到的每個整數求平方，然後將平方後的結果通過第二個channel發送給第三個goroutine；第三個goroutine是一個打印程序，打印收到的每個整數。爲了保持例子清晰，我們有意選擇了非常簡單的函數，當然三個goroutine的計算很簡單，在現實中確實沒有必要爲如此簡單的運算構建三個goroutine。

```Go
gopl.io/ch8/pipeline1

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實現：

```Go
close(naturals)
```

當一個channel被關閉後，再向該channel發送數據將導致panic異常。當一個被關閉的channel中已經發送的數據都被成功接收後，後續的接收操作將不再阻塞，它們會立卽返迴一個零值。關閉上面例子中的naturals變量對應的channel併不能終止循環，它依然會收到一個永無休止的零值序列，然後將它們發送給打印者goroutine。

沒有辦法直接測試一個channel是否被關閉，但是接收操作有一個變體形式：它多接收一個結果，多接收的第二個結果是一個布爾值ok，ture表示成功從channels接收到值，false表示channels已經被關閉併且里面沒有值可接收。使用這個特性，我們可以脩改squarer函數中的循環代碼，當naturals對應的channel被關閉併沒有值可接收時跳出循環，併且也關閉squares對應的channel.

```Go
// 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也可以正常終止循環併退出程序。

```Go
gopl.io/ch8/pipeline2

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還會觸發一個廣播機製，我們將在8.9節討論。


