> 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 已經滿了。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://go.netdpi.net/concurrency/10.02-channel.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
