go context 上下文簡單介紹和使用
//context.WithDeadline() // 指定一個終止時間 time
//context.WithTimeout() // 自定義超時時間 time
//context.WithValue() // 自定義一個鍵值對 取值 map[key]value
//ctx,cancel:=context.WithCancel(context.Background()) // 獲取一個終止函數
// 設置一個50毫秒的超時
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
key := TraceCode("TRACE_CODE")
traceCode, ok := ctx.Value(key).(string) // 在子goroutine中獲取trace code
if !ok {
fmt.Println("invalid trace code")
}
// 設置一個50毫秒的超時
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
// 在系統的入口中設置trace code傳遞給后續啟動的goroutine實現日志數據聚合
ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "12512312234")
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// 盡管ctx會過期,但在任何情況下調用它的cancel函數都是很好的實踐。
// 如果不這樣做,可能會使上下文及其父類存活的時間超過必要的時間。
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
context.Context是一個接口,該接口定義了四個需要實現的方法。具體簽名如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
其中:
Deadline方法需要返回當前Context被取消的時間,也就是完成工作的截止時間(deadline);
Done方法需要返回一個Channel,這個Channel會在當前工作完成或者上下文被取消之后關閉,多次調用Done方法會返回同一個Channel;
Err方法會返回當前Context結束的原因,它只會在Done返回的Channel被關閉時才會返回非空的值;
如果當前Context被取消就會返回Canceled錯誤;
如果當前Context超時就會返回DeadlineExceeded錯誤;
Value方法會從Context中返回鍵對應的值,對於同一個上下文來說,多次調用Value 並傳入相同的Key會返回相同的結果,該方法僅用於傳遞跨API和進程間跟請求域的數據;
Background()和TODO()
Go內置兩個函數:Background()和TODO(),這兩個函數分別返回一個實現了Context接口的background和todo。我們代碼中最開始都是以這兩個內置的上下文對象作為最頂層的partent context,衍生出更多的子上下文對象。
Background()主要用於main函數、初始化以及測試代碼中,作為Context這個樹結構的最頂層的Context,也就是根Context。
TODO(),它目前還不知道具體的使用場景,如果我們不知道該使用什么Context的時候,可以使用這個。
background和todo本質上都是emptyCtx結構體類型,是一個不可取消,沒有設置截止時間,沒有攜帶任何值的Context。
官方案例
var (
ctx context.Context
cancel context.CancelFunc
)
func handleSearch(w http.ResponseWriter, req *http.Request) {
// ctx 上下文管理 簡單案例演示
timeout, err := time.ParseDuration(req.FormValue("timeout"))
if err == nil {
// 初始化 上下文並獲取對象
ctx, cancel = context.WithTimeout(context.Background(), timeout)
} else {
ctx, cancel = context.WithCancel(context.Background())
}
// 延時關閉
defer cancel()
}
demo代碼演示二:
var wg sync.WaitGroup
func handleSearch(ctx context.Context) {
for {
time.Sleep(time.Second)
fmt.Println("working……")
select {
case <-ctx.Done():
return
default:
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1) //添加
// 如何實現go 子程序優雅的退出
go handleSearch(ctx)
// 案例監聽5秒
time.Sleep(time.Second * 5)
// 連續5秒都沒完成則自動退出
cancel() // 退出
wg.Wait() // 等待結束
fmt.Println("over")
}
推薦以參數的方式顯示傳遞Context
以Context作為參數的函數方法,應該把Context作為第一個參數。
給一個函數方法傳遞Context的時候,不要傳遞nil,如果不知道傳遞什么,就使用context.TODO()
Context的Value相關方法應該傳遞請求域的必要數據,不應該用於傳遞可選參數
Context是線程安全的,可以放心的在多個goroutine中傳遞