> For the complete documentation index, see [llms.txt](https://go.netdpi.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://go.netdpi.net/concurrency/10.02-channel.md).

# 10.02-Channel

Channel (頻道) 提供一種方式，供兩個 goroutines 可以互相通信，並彼此同步執行。下列是一個使用 channel 的範例程式：

```go
package main

import (
    "fmt"
    "time"
)

func pinger(c chan string) {
    for i := 0; ; i++ {
        c <- "ping"
    }
}
func printer(c chan string) {
    for {
        msg := <- c
        fmt.Println(msg)
        time.Sleep(time.Second * 1)
    }
}
func main() {
    var c chan string = make(chan string)
    
    go pinger(c)
    go printer(c)
    
    var input string
    fmt.Scanln(&input)
}
```

此程式將會持續印出 "ping"（直到按下 Enter 為止）。一個 channel type (頻道型別) 的表示方式是使用關鍵字 `chan` 以及接著要傳遞給 channel 的資料之型別（在此例中，我們傳遞字串）。

而 `<-` （左箭頭）運算符則用於傳送與接收 channel 的訊息，`c <- "ping"` 表示送出 `"ping"`，而 `msg := <- c` 表示接收一個訊息，並將訊息儲存於 `msg`。`fmt` 這行也可以寫成：`fmt.Println(<-c)`，在此例我們可以移除前面那行。

像這樣使用一個 channel 來同步兩個 two goroutines。當 `pinger` 試圖想要送出該 channel 上的一筆訊息時，它會持續等待（即所謂的阻塞），直到 `printer` 已經準備好要接收訊息為止。我們將另一個傳送端加到程式中，看會發生什麼事情。新增如下的函式：

```go
func ponger(c chan string) {
    for i := 0; ; i++ {
        c <- "pong"
    }
}
```

再修改 `main`：

```go
func main() {
    var c chan string = make(chan string)
    
    go pinger(c)
    go ponger(c)
    go printer(c)
    
    var input string
    fmt.Scanln(&input)
}
```

這個程式現在會輪流印出 “ping” 跟 “pong”。

#### Channel Direction <a href="#toc-channel-direction" id="toc-channel-direction"></a>

我們可以對一個 channel 型別指定 channel direction 來限制它是要做為傳送或是接收。例如：會將 pinger 的函式特徵值修改如下：

```go
func pinger(c chan<- string)
```

現在 `c` 只能用來傳送，若企圖對 c 進行接收資料將會導致一個編譯的錯誤，我們一樣可以將 printer 改為如下：

```go
func printer(c <-chan string)
```

若 channel 沒有方向限制，則是所謂的雙向（bi-directional），可以將一個雙向的 channel 傳遞給一個函式做為只能傳送或只能接收的 channel 使用，反之則不可。

#### Select <a href="#toc-select" id="toc-select"></a>

Go 有一個特殊的陳述句，稱為 `select`，它的作用與 `switch` 類似，但只能用在 channels：

```go
func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    
    go func() {
        for {
            c1 <- "from 1"
            time.Sleep(time.Second * 2)
        }
    }()
    go func() {
        for {
            c2 <- "from 2"
            time.Sleep(time.Second * 3)
        }
    }()
    go func() {
        for {
            select {
            case msg1 := <- c1:
                fmt.Println(msg1)
            case msg2 := <- c2:
                fmt.Println(msg2)
            }
        }
    }()
    
    var input string
    fmt.Scanln(&input)
}
```

這個程式每隔兩秒會輸出一次 “from 1”，而每隔三秒會印出一次 “from 2”。`select` 會選取第一個已經就緒的 channel 來接收（或傳送）資料。若同時有多個 channels 已經就緒，則隨機選取一個 channel 來進行接收。若沒有任何 channel 已經就緒，則會發生阻塞，直到有任何一個 channel 已經就緒為止。

`select` 陳述句也很常用來實做計時工作（timeout）：

```go
select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
}
```

`time.After` 會建立一個 channel，並在指定的時間週期之後送出它所在的目前時間（我們用不到這個時間，所以這裡不會把時間存到變數）。我們也能指定一個 `default` 的情況：

```go
select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nothing ready")
}
```

若沒有任何 channel 已經就緒，則會進去 default 這個情況。

#### Buffered Channel <a href="#toc-buffered-channels" id="toc-buffered-channels"></a>

在建立一個 channel 時，也可以傳遞第二個參數給 make 函式：

```go
c := make(chan int, 1)
```

這樣會建立一個容量為1的 buffered channel（有緩衝的頻道），通常 channel 是同步的，channel 兩端會互相等待彼此已經就緒。不過一個有緩衝的 channel 則是非同步的（asynchronous），在傳送或接收一筆訊息時都不用等待，除非 channel 已經滿了。
