深入了解Go語言中context的用法
很多 Go 項目的源碼,在讀的過程中會發(fā)現(xiàn)一個很常見的參數(shù) ctx,而且基本都是作為函數(shù)的第一個參數(shù)。
為什么要這么寫呢?這個參數(shù)到底有什么用呢?帶著這樣的疑問,我研究了這個參數(shù)背后的故事。
開局一張圖:

核心是 Context 接口:
//?A?Context?carries?a?deadline,?cancelation?signal,?and?request-scoped?values
//?across?API?boundaries.?Its?methods?are?safe?for?simultaneous?use?by?multiple
//?goroutines.
type?Context?interface?{
????//?Done?returns?a?channel?that?is?closed?when?this?Context?is?canceled
????//?or?times?out.
????Done()?<-chan?struct{}
????//?Err?indicates?why?this?context?was?canceled,?after?the?Done?channel
????//?is?closed.
????Err()?error
????//?Deadline?returns?the?time?when?this?Context?will?be?canceled,?if?any.
????Deadline()?(deadline?time.Time,?ok?bool)
????//?Value?returns?the?value?associated?with?key?or?nil?if?none.
????Value(key?interface{})?interface{}
}包含四個方法:
Done():返回一個 channel,當(dāng) times out 或者調(diào)用 cancel 方法時。Err():返回一個錯誤,表示取消 ctx 的原因。Deadline():返回截止時間和一個 bool 值。Value():返回 key 對應(yīng)的值。
有四個結(jié)構(gòu)體實現(xiàn)了這個接口,分別是:emptyCtx, cancelCtx, timerCtx 和 valueCtx。
其中 emptyCtx 是空類型,暴露了兩個方法:
func?Background()?Context func?TODO()?Context
一般情況下,會使用 Background() 作為根 ctx,然后在其基礎(chǔ)上再派生出子 ctx。要是不確定使用哪個 ctx,就使用 TODO()。
另外三個也分別暴露了對應(yīng)的方法:
func?WithCancel(parent?Context)?(ctx?Context,?cancel?CancelFunc)
func?WithDeadline(parent?Context,?deadline?time.Time)?(Context,?CancelFunc)
func?WithTimeout(parent?Context,?timeout?time.Duration)?(Context,?CancelFunc)
func?WithValue(parent?Context,?key,?val?interface{})?Context遵循規(guī)則
在使用 Context 時,要遵循以下四點規(guī)則:
- 不要將 Context 放入結(jié)構(gòu)體,而是應(yīng)該作為第一個參數(shù)傳入,命名為
ctx。 - 即使函數(shù)允許,也不要傳入
nil的 Context。如果不知道用哪種 Context,可以使用context.TODO()。 - 使用 Context 的 Value 相關(guān)方法只應(yīng)該用于在程序和接口中傳遞和請求相關(guān)的元數(shù)據(jù),不要用它來傳遞一些可選的參數(shù)。
- 相同的 Context 可以傳遞給不同的 goroutine;Context 是并發(fā)安全的。
WithCancel
func?WithCancel(parent?Context)?(ctx?Context,?cancel?CancelFunc)
WithCancel 返回帶有新 Done 通道的父級副本。當(dāng)調(diào)用返回的 cancel 函數(shù)或關(guān)閉父上下文的 Done 通道時,返回的 ctx 的 Done 通道將關(guān)閉。
取消此上下文會釋放與其關(guān)聯(lián)的資源,因此在此上下文中運行的操作完成后,代碼應(yīng)立即調(diào)用 cancel。
舉個例子:
這段代碼演示了如何使用可取消上下文來防止 goroutine 泄漏。在函數(shù)結(jié)束時,由 gen 啟動的 goroutine 將返回而不會泄漏。
package?main
import?(
????"context"
????"fmt"
)
func?main()?{
????//?gen?generates?integers?in?a?separate?goroutine?and
????//?sends?them?to?the?returned?channel.
????//?The?callers?of?gen?need?to?cancel?the?context?once
????//?they?are?done?consuming?generated?integers?not?to?leak
????//?the?internal?goroutine?started?by?gen.
????gen?:=?func(ctx?context.Context)?<-chan?int?{
????????dst?:=?make(chan?int)
????????n?:=?1
????????go?func()?{
????????????for?{
????????????????select?{
????????????????case?<-ctx.Done():
????????????????????return?//?returning?not?to?leak?the?goroutine
????????????????case?dst?<-?n:
????????????????????n++
????????????????}
????????????}
????????}()
????????return?dst
????}
????ctx,?cancel?:=?context.WithCancel(context.Background())
????defer?cancel()?//?cancel?when?we?are?finished?consuming?integers
????for?n?:=?range?gen(ctx)?{
????????fmt.Println(n)
????????if?n?==?5?{
????????????break
????????}
????}
}輸出:
1
2
3
4
5
WithDeadline
func?WithDeadline(parent?Context,?d?time.Time)?(Context,?CancelFunc)
WithDeadline 返回父上下文的副本,并將截止日期調(diào)整為不晚于 d。如果父級的截止日期已經(jīng)早于 d,則 WithDeadline(parent, d) 在語義上等同于 parent。
當(dāng)截止時間到期、調(diào)用返回的取消函數(shù)時或當(dāng)父上下文的 Done 通道關(guān)閉時,返回的上下文的 Done 通道將關(guān)閉。
取消此上下文會釋放與其關(guān)聯(lián)的資源,因此在此上下文中運行的操作完成后,代碼應(yīng)立即調(diào)用取消。
舉個例子:
這段代碼傳遞具有截止時間的上下文,來告訴阻塞函數(shù),它應(yīng)該在到達截止時間時立刻退出。
package?main
import?(
????"context"
????"fmt"
????"time"
)
const?shortDuration?=?1?*?time.Millisecond
func?main()?{
????d?:=?time.Now().Add(shortDuration)
????ctx,?cancel?:=?context.WithDeadline(context.Background(),?d)
????//?Even?though?ctx?will?be?expired,?it?is?good?practice?to?call?its
????//?cancellation?function?in?any?case.?Failure?to?do?so?may?keep?the
????//?context?and?its?parent?alive?longer?than?necessary.
????defer?cancel()
????select?{
????case?<-time.After(1?*?time.Second):
????????fmt.Println("overslept")
????case?<-ctx.Done():
????????fmt.Println(ctx.Err())
????}
}輸出:
context deadline exceeded
WithTimeout
func?WithTimeout(parent?Context,?timeout?time.Duration)?(Context,?CancelFunc)
WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。
取消此上下文會釋放與其關(guān)聯(lián)的資源,因此在此上下文中運行的操作完成后,代碼應(yīng)立即調(diào)用取消。
舉個例子:
這段代碼傳遞帶有超時的上下文,以告訴阻塞函數(shù)應(yīng)在超時后退出。
package?main
import?(
????"context"
????"fmt"
????"time"
)
const?shortDuration?=?1?*?time.Millisecond
func?main()?{
????//?Pass?a?context?with?a?timeout?to?tell?a?blocking?function?that?it
????//?should?abandon?its?work?after?the?timeout?elapses.
????ctx,?cancel?:=?context.WithTimeout(context.Background(),?shortDuration)
????defer?cancel()
????select?{
????case?<-time.After(1?*?time.Second):
????????fmt.Println("overslept")
????case?<-ctx.Done():
????????fmt.Println(ctx.Err())?//?prints?"context?deadline?exceeded"
????}
}輸出:
context deadline exceeded
WithValue
func?WithValue(parent?Context,?key,?val?any)?Context
WithValue 返回父級的副本,其中與 key 關(guān)聯(lián)的值為 val。
其中鍵必須是可比較的,并且不應(yīng)是字符串類型或任何其他內(nèi)置類型,以避免使用上下文的包之間發(fā)生沖突。 WithValue 的用戶應(yīng)該定義自己的鍵類型。
為了避免分配給 interface{},上下文鍵通常具有具體的 struct{} 類型?;蛘撸瑢?dǎo)出的上下文鍵變量的靜態(tài)類型應(yīng)該是指針或接口。
舉個例子:
這段代碼演示了如何將值傳遞到上下文以及如何檢索它(如果存在)。
package?main
import?(
????"context"
????"fmt"
)
func?main()?{
????type?favContextKey?string
????f?:=?func(ctx?context.Context,?k?favContextKey)?{
????????if?v?:=?ctx.Value(k);?v?!=?nil?{
????????????fmt.Println("found?value:",?v)
????????????return
????????}
????????fmt.Println("key?not?found:",?k)
????}
????k?:=?favContextKey("language")
????ctx?:=?context.WithValue(context.Background(),?k,?"Go")
????f(ctx,?k)
????f(ctx,?favContextKey("color"))
}輸出:
found value: Go
key not found: color
本文的大部分內(nèi)容,包括代碼示例都是翻譯自官方文檔,代碼都是經(jīng)過驗證可以執(zhí)行的。如果有不是特別清晰的地方,可以直接去讀官方文檔。
以上就是深入了解Go語言中context的用法的詳細(xì)內(nèi)容,更多關(guān)于Go context的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何使用Go語言實現(xiàn)基于泛型的Jaccard相似度算法
這篇文章主要介紹了如何使用Go語言實現(xiàn)基于泛型的Jaccard相似度算法,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-08-08

