跳至主要內容
yomihon 書庫
整體狀況設定

鍵盤快捷鍵

⌘K / Ctrl K
搜尋
Esc
關閉搜尋或篩選
/
跳到導覽篩選(單鍵開啟時)
[
收合或展開側欄(單鍵開啟時)

關掉之後,/ 和 [ 都只會照瀏覽器原本的方式輸入;⌘K 與 Esc 不受影響。

設定
esc

↑↓ 選擇 ↵ 搜尋 esc 關閉

G04 取消不再需要的工作

lesson檔案變更於
檔案變更於
類型
lesson
語言
zh-Hant
在 Obsidian 開啟留下自己的想法
檔案變更於
類型
lesson
語言
zh-Hant
在 Obsidian 開啟留下自己的想法
本頁內容 以這篇為來源的筆記 1 對照閱讀 1 正文連到這篇 4

接收者有時只要第一個結果。若它離開後,producer 還卡在下一次發送,工作就無法收尾。

把發送與取消放進同一個 select:能交出結果就繼續;收到取消就返回。ctx.Done() 是取消通知的 channel。1

producer 經由 out 傳值給 consumer;consumer 不再接收時呼叫 cancel,producer 的 select 收到 ctx.Done 後返回並關閉輸出。

package main

import (
	"context"
	"fmt"
	"sync"
)

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

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	var wg sync.WaitGroup
	values := squares(ctx, &wg)

	fmt.Println(<-values)
	cancel()
	wg.Wait()
	fmt.Println("已收尾")
}

cancel 與 Wait 能交換嗎?

main 只接收一個值。若先 wg.Wait(),等它返回後才 cancel(),還能結束嗎?

查看執行結果

不能。producer 的下一次發送沒有接收者,Wait() 又在等 producer 返回;取消通知永遠到不了。

原程式先取消,producer 才能離開等待並執行 Done()。輸出是:

4
已收尾

cancel() 只通知工作停止;Wait() 才等待退出。2

另一種寫法以 done 取代 WaitGroup,分開取消與完成通知:

出自 可以取消的管線

兩個訊號,各管一件事

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

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

取消需要工作配合

如果把 select 改回單獨的 out <- n*n,取消也救不了卡住的發送。每個可能長時間等待的地方,都要有退出方式;耗時函式也必須接受 context 或自行檢查取消。取消與收尾 整理了這兩個責任。

select 不保證取消優先。若發送和取消同時可進行,仍可能選到發送;本例取消後不再接收,無緩衝的發送無法完成。逾時與多個就緒分支見 G06 select 與逾時。

提早返回的管線 只讓接收者離開,上游會停在發送;可取消的版本為這個等待補上退出條件,並由 producer 關閉輸出。

取消與收尾

concept檔案變更於
檔案變更於
類型
concept
語言
zh-Hant
在 Obsidian 開啟留下自己的想法
檔案變更於
類型
concept
語言
zh-Hant
在 Obsidian 開啟留下自己的想法
聲明的來源 2 正文連到這篇 2

取消是通知,收尾是確認工作已結束。cancel() 不會強制終止 goroutine,也不等待它返回。

可能阻塞的 send 要有另一條出口:

select {
case out <- value:
case <-ctx.Done():
    return
}

select 同時有多個可執行分支時,不保證取消優先。無緩衝的 channel 沒有接收方時,send 不能繼續;取消生效後,便可走取消分支。

需要確認收尾時,另用 WaitGroup 或完成通知。不要把「已呼叫 cancel」當成「工作已結束」。

context.CancelFunc(另開分頁)、Select statements(另開分頁)