---
title: 可以取消的管線
aliases: [cancellable pipeline]
type: note
status: ready
domain: go
topics: [channel, context, 取消]
created: 2026-09-22
lang: zh-Hant
---

消費者只需要第一個平方。取到後，它通知 producer 停止，並等待收尾。

```go
package main

import (
    "context"
    "fmt"
)

func squares(ctx context.Context) (<-chan int, <-chan struct{}) {
    out := make(chan int)
    done := make(chan struct{})
    go func() {
        defer close(done)
        defer close(out)
        for _, n := range []int{1, 2, 3} {
            select {
            case out <- n * n:
            case <-ctx.Done():
                return
            }
        }
    }()
    return out, done
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    values, done := squares(ctx)
    fmt.Println(<-values)
    cancel()
    <-done
    fmt.Println("stopped")
}
```

儲存為 `main.go`，執行 `go run main.go`。Go 1.27.0 的輸出：

```text
1
stopped
```

## 兩個訊號，各管一件事

`ctx.Done()` 通知 producer 取消；`done` 關閉則表示 producer 已完成收尾。`cancel()` 本身不等待工作結束。

消費者不再接收時，send 無法繼續。取消訊號讓 `select` 走到 `return`，接著執行 `defer`：先關閉 `out`，再關閉 `done`。

## 誰關閉 channel

producer 是 `out` 的唯一發送者，由它關閉最容易確認不會再有 send。消費者直接關閉 `out`，反而可能讓 producer 向已關閉的 channel 發送而 panic。 ^owner

> [!warning] 取消沒有優先權
> 若接收與取消同時可行，`select` 可能選中 send。本例取走第一個值後就不再接收，因此取消能解除後續 send 的阻塞。

這個模式只涵蓋 channel 等待；計算或 I/O 也必須提供自己的取消方式。[^context]

[^context]: [context.CancelFunc](https://pkg.go.dev/context#CancelFunc) 說明取消不等待工作停止；[Go pipelines](https://go.dev/blog/pipelines) 討論下游提早離開時，上游的退出責任。
