核心是個 channel
概述
context package 是 Go 1.7 版推出的套件,用於程序間傳遞截止時間 (deadlines)、 取消訊號 (cancellation signals), 以及其他(比方說API查詢時)所帶的值 (request-scoped values)。
這件事情不是不能靠 channel 做到,況且 context package 還是基於 channels 去做到傳遞訊號的功能,(以下是我的推測) 但在官方正式把 context 納入標準庫之前,實踐方法應該是五花八門。 context 的推出,某種程度統一了混亂的局面 (推測結束)。
context 也應用在官方的 http.Request 之中,request 內就包了一個 context,因此 context 現在可說是用 Go 寫後端的標配。
情境
如下圖,A是一系列程序的頭,分支出許多子程序,子程序又有子程序,那該如何從源頭取消整個系列的程序?
A
/ | \
B C D
/ \ /|\
E F G H I
最簡單的方法是宣告一個 channel 去傳遞取消訊號,再讓所有子程序都聽這個 channel 。
到了 context 的年代原理也是一樣,但由於諸多考量,所以把 channel 包在 context 之中:宣告一個 context,內含一個 channel 去傳遞取消訊號,再將這個 context 作為參數傳入所有子程序,子程序都聽這個 context 夾帶的 channel。
規範
Context必須在整串程序中傳遞下去。
package 說明原文是這樣寫的:
Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. When a Context is canceled, all Contexts derived from it are also canceled.
而且,這個傳遞必須是顯性的,放在第一個參數位置,命名為ctx,不能包在別的 struct 之中。
Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx.
或許有人開始擔心,ctx 的傳遞,如果在 goroutines 很大量的情況下,會不會造成影響?其實不會,因為 ctx 本身是 pointer。
簡單的例子
下面這個例子中,ctx 是 goroutine function 的第一個參數,worker 會倒數,會無限執行,直到 main goroutine 呼叫了 cancel():
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context, done chan struct{}, i int) {
for range time.Tick(time.Second) {
select {
case <-ctx.Done():
fmt.Println("Canceled by ctx signal.")
fmt.Printf("%T\n", ctx)
done <- struct{}{}
default:
fmt.Println(i)
i -= 1
}
}
fmt.Println("This line will never been executed.")
done <- struct{}{}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
CntDown := 3
go worker(ctx, done, CntDown)
time.Sleep(time.Duration(CntDown) * time.Second)
cancel()
<-done
}
結果
3
2
1
Canceled by ctx signal.
*context.cancelCtx
Context 的行為類型
我將 context 的行為分成兩種類型以便理解:他殺、定時。
他殺
這類的 context 會伴隨產生一個 cancel function,用來讓父程序殺死整串子程序。
定時
這類的 context 除了會伴隨產生一個 cancel function,還可先定義大限之日或時限,時間到了,整串子程序就會被殺。常被用於限制 requests 的執行時間。
透過 Context 夾帶 Key Value
假如說有資料要傳遞給子程序,而這資料只活在這串程序中,程序結束就可拋棄,可以透過 context 夾帶。
透過定時改寫上面範例
// 前略
func main() {
CntDown := 3
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(CntDown)*time.Second)
defer cancel()
done := make(chan struct{})
go worker(ctx, done, CntDown)
<-done
}