# **管道**
管道是一個虛擬的方法用來連接 goroutines 和 通道,使一個 goroutine 的輸出成為另一個的輸入,使用通道傳遞數據。
使用管道的一個好處是程序中有不變的數據流,因此 goroutine 和 通道不必等所有都就緒才開始執行。另外,因為你不必把所有內容都保存為變量,就節省了變量和內存空間的使用。最后,管道簡化了程序設計并提升了維護性。
我們使用 `pipeline.go` 代碼來說明管道的使用。這個程序分六部分來介紹。`pipeling.go` 程序執行的任務是在給定范圍內產生隨機數,當任何數字隨機出現第二次時就結束。但在終止前,程序將打印第一個隨機數出現第二次之前的所有隨機數之和。你需要三個函數來連接程序的通道。程序的邏輯在這三個函數中,但數據流在管道的通道內。
這個程序有兩個通道。第一個(通道A)用于從第一個函數獲取隨機數并發送它們到第二個函數。第二個(通道B)被第二個函數用來發送可接受的隨機數到第三個函數。第三個函數負責從通道 B 獲取數據,并計算結果和展示。
`pipeline.go` 的第一段代碼如下:
```go
package main
import (
"fmt"
"math/rand"
"os"
"strconv"
"time"
)
var CLOSEA = false
var DATA = make(map[int]bool)
```
因為 `second()` 函數需要通過一種方式告訴 `first()` 函數關閉第一個通道,所以我使用一個全局變量 `CLOSEA` 來處理。`CLOSEA` 變量只在 `first()` 函數中檢查,并只能在 `second()` 函數中修改。
`pipeline.go` 的第二段代碼如下:
```go
func random(min, max int) int {
return rand.Intn(max-min) + min
}
func first(min, max int, out chan<- int) {
for {
if CLOSEA {
close(out)
return
}
out <- random(min, max)
}
}
```
上面的代碼展示了 `random` 和 `first` 函數的實現。你已經對 `random()` 函數比較熟悉了,它在一定范圍內產生隨機數。但真正有趣的是 `first()` 函數。它使用 `for` 循環持續運行,直到 `CLOSEA` 變為 `true`。那樣的話,它就關閉 `out` 通道。
`pipeline.go` 的第三段代碼如下:
```go
func second(out chan<- int, in <-chan int) {
for x := range in {
fmt.Print(x, " ")
_, ok := DATA[x]
if ok {
CLOSEA = true
} else {
DATA[x] = true
out <- x
}
}
fmt.Println()
close(out)
}
```
`second()` 函數從 `in` 通道接收數據,并發送該數據到 `out` 通道。但是,`second()` 函數一旦發現 `DATA` map 中已經存在了該隨機數,它就把 `CLOSEA` 全局變量設為 `true` 并停止發送任何數據到 `out` 通道。然后,它就關閉 `out` 通道。
`pipeline.go` 的第四段代碼如下:
```go
func third(in <-chan int){
var sum int
sum = 0
for x2 := range in {
sum = sum + x2
}
fmt.Println("The sum of the random numbers is %d\n", sum)
}
```
`third` 函數持續從 `in` 通道讀取數據。當通道被 `second()` 函數關閉時,`for` 循環停止獲取數據,函數打印輸出。從這很清楚的看到 `second()` 函數控制許多事情。
`pipeline.go` 的第五段代碼如下:
```go
func main() {
if len(os.Args) != 3 {
fmt.Println("Need two integer paramters!")
os.Exit(1)
}
n1, _ := strconv.Atoi(os.Args[1])
n2, _ := strconv.Atoi(os.Args[2])
if n1 > n2 {
fmt.Printf("%d should be smaller than %d\n", n1, n2)
return
}
```
`pipeline.go` 的最后一段如下:
```go
rand.Seed(time.Now().UnixNano())
A := make(chan int)
B := make(chan int)
go first(n1, n2, A)
go second(B, A)
third(B)
}
```
這里,定義了需要的通道,并執行兩個 `goroutines` 和一個函數。`third()` 函數阻止 `main` 返回,因為它不作為 `goroutine` 執行。
執行 `pipeline.go` 產生如下輸出:
```shell
$go run pipeline.go 1 10
2 2
The sum of the random numbers is 2
$go run pipeline.go 1 10
9 7 8 4 3 3
The sum of the random numbers is 31
$go run pipeline.go 1 10
1 6 9 7 1
The sum of the random numbers is 23
$go run pipeline.go 10 20
16 19 16
The sum of the random numbers is 35
$go run pipeline.go 10 20
10 16 17 11 15 10
The sum of the random numbers is 69
$go run pipeline.go 10 20
12 11 14 15 10 15
The sum of the random numbers is 62
```
這里重點是盡管 `first` 函數按自己的節奏持續產生隨機數,并且 `second()` 函數把它們打印在屏幕上,不需要的隨機數也就是已經出現的隨機數,不會發送給 `third()` 函數,因此就不會包含在最終的總和中。
- 介紹
- 1 Go與操作系統
- 01.1 Go的歷史
- 01.2 Go的未來
- 01.3 Go的優點
- 01.3.1 Go是完美的么
- 01.3.2 什么是預處理器
- 01.3.3 godoc
- 01.4 編譯Go代碼
- 2 理解 Go 的內部構造
- Go 編譯器
- Go 的垃圾回收
- 三色算法
- 有關 Go 垃圾收集器操作的更多信息
- Maps, silces 與 Go 垃圾回收器
- Unsafe code
- 有關 unsafe 包
- 另一個 usafe 包的例子
- 從 Go 調用 C 代碼
- 在同一文件用 Go 調用 C 代碼
- 在單獨的文件用 Go 調用 C 代碼
- 從 C 調用 Go 代碼
- Go 包
- C 代碼
- defer 關鍵字
- 用 defer 打印日志
- Panic 和 Recover
- 單獨使用 Panic 函數
- 兩個好用的 UNIX 工具
- strace
- dtrace
- 配置 Go 開發環境
- go env 命令
- Go 匯編器
- 節點樹
- 進一步了解 Go 構建
- 創建 WebAssembly 代碼
- 對 Webassembly 的簡單介紹
- 為什么 WebAssembly 很重要
- Go 與 WebAssembly
- 示例
- 使用創建好的 WebAssembly 代碼
- Go 編碼風格建議
- 練習和相關鏈接
- 本章小結
- 3 Go基本數據類型
- 03.1 Go循環
- 03.1.1 for循環
- 03.1.2 while循環
- 03.1.3 range關鍵字
- 03.1.4 for循環代碼示例
- 03.3 Go切片
- 03.3.1 切片基本操作
- 03.3.2 切片的擴容
- 03.3.3 字節切片
- 03.3.4 copy()函數
- 03.3.5 多維切片
- 03.3.6 使用切片的代碼示例
- 03.3.7 使用sort.Slice()排序
- 03.4 Go 映射(map)
- 03.4.1 Map值為nil的坑
- 03.4.2 何時該使用Map?
- 03.5 Go 常量
- 03.5.1 常量生成器:iota
- 03.6 Go 指針
- 03.7 時間與日期的處理技巧
- 03.7.1 解析時間
- 03.7.2 解析時間的代碼示例
- 03.7.3 解析日期
- 03.7.4 解析日期的代碼示例
- 03.7.5 格式化時間與日期
- 03.8 延伸閱讀
- 03.9 練習
- 03.10 本章小結
- 9 并發-Goroutines,Channel和Pipeline
- 09.1 關于進程,線程和Go協程
- 09.1.1 Go調度器
- 09.1.2 并發與并行
- 09.2 Goroutines
- 09.2.1 創建一個Goroutine
- 09.2.2 創建多個Goroutine
- 09.3 優雅地結束goroutines
- 09.3.1 當Add()和Done()的數量不匹配時會發生什么?
- 09.4 Channel(通道)
- 09.4.1 通道的寫入
- 09.4.2 從通道接收數據
- 09.4.3 通道作為函數參數傳遞
- 09.5 管道
- 09.6 延展閱讀
- 09.7 練習
- 09.8 本章小結