好的,今天來介紹Context
套件吧!之前在提goroutine
時有用過兩種方式來終結goroutine
,一種是設置sync.WaitGroup
,另一種是用channel
控制,而Context
又是另一種在各個goroutine
間傳送訊號的方式,通常用來送出取消或結束的訊號,是Go在1.7版本時放入標準函式庫的套件。
官方文件在這邊
官方文件中列出了幾件事情要注意的
Context
存在struct
中Context
建立出來,並當作參數傳遞給需要使用到的函式Context
放在第一個參數,並命名ctx
Context
並傳入nil
,若還不清楚要用於什麼情況可使用TODO
方式宣告更靈活的操作各種goroutine
,可主動結束或是設置結束條件,以http
請求來說,若請求超時的話就必須重啟或斷掉請求,或是由使用者送出結束請求時要將進行中的goroutine
關掉,Context
很適合運用在這些地方。
套件提供兩種方法宣告Context
context.Background()
: 返回一個非nil
的Context
,通常用於最上層的傳入,像是main
、init
、tests
。context.TODO()
:返回一個非nil
的Context
,當還未清楚要如何使用或尚不可用時可以使用TODO
方式宣告。反正大多時候是使用第一種方式建立Context
,我學藝不清還沒遇到要使用下面宣告的狀況。
在建立出父類別Context
後,可以透過四個With
函式使用不同的Context
並應對各種場景。
// 設置cancle,可主動關閉所有相關的goroutine
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// 設置結束時間,在結束時間到時結束相關goroutine
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
// 設置超時偵測,在超過預期時間時結束相關goroutine
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
// 設置共通變數,可在相關goroutine中都使用此變數
func WithValue(parent Context, key, val interface{}) Context
使用方式如下,宣告最上層Context
後傳入With
函式,並將ctx
傳入需要的函式。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
go repeat(ctx) // 執行無窮迴圈程式
time.Sleep(3 * time.Second)
fmt.Println("使用cancel()")
cancel()
time.Sleep(1 * time.Second)
}
func repeat(ctx context.Context){
for {
select {
case <-ctx.Done():
fmt.Println("閉嘴啦吵死了")
return
default:
fmt.Println("兔子兔子兔子兔子兔子")
time.Sleep(1 * time.Second)
}
}
}
output:
兔子兔子兔子兔子兔子
兔子兔子兔子兔子兔子
兔子兔子兔子兔子兔子
使用cancel()
閉嘴啦吵死了
兩個使用方式差不多,我這邊試寫Timeout
就好,輸出可以看到執行三秒後程式就自己終止了。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
borrowRabbit(ctx)
time.Sleep(time.Second)
}
func borrowRabbit(ctx context.Context){
for {
select {
case <-ctx.Done():
fmt.Println("兔子回汐止了")
return
default:
fmt.Println("阿現在兔兔在我這")
time.Sleep(1 * time.Second)
}
}
}
output:
阿現在兔兔在我這
阿現在兔兔在我這
阿現在兔兔在我這
兔子回汐止了
這個Context
不支援CancelFunc
,WithValue
的功用是夾帶遷入的value
並傳遞在各個子Context
間。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.WithValue(context.Background(), "price", "17 million")
Get(ctx, "price")
go func() {
// 這邊將父Context傳入建立ctx2
ctx2 := context.WithValue(ctx, "name", "SM兔兔")
Get(ctx2, "price") // 從ctx2讀取父Context的資料
go func() {
Get(ctx2, "name")
}()
}()
time.Sleep(1 * time.Second)
}
// 取得對應key值之value
func Get(ctx context.Context, k string) {
if v, ok := ctx.Value(k).(string); ok {
fmt.Println(k, v)
}
}
Context
的部分差不多介紹到這,除了內建的四種使用方法也可以自定義Context
,算是個滿方便的功能,熟練使用的話也能更好的管理goroutine
。
Document
https://pkg.go.dev/context
【Go语言】小白也能看懂的context包详解:从入门到精通
https://segmentfault.com/a/1190000040917752
[pkg] context
https://pjchender.dev/golang/pkg-context/
Go Context 101
https://medium.com/codex/go-context-101-ebfaf655fa95