深入了解Go語言中context的用法
很多 Go 項(xiàng)目的源碼,在讀的過程中會(huì)發(fā)現(xiàn)一個(gè)很常見的參數(shù) ctx
,而且基本都是作為函數(shù)的第一個(gè)參數(shù)。
為什么要這么寫呢?這個(gè)參數(shù)到底有什么用呢?帶著這樣的疑問,我研究了這個(gè)參數(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{} }
包含四個(gè)方法:
Done()
:返回一個(gè) channel,當(dāng) times out 或者調(diào)用 cancel 方法時(shí)。Err()
:返回一個(gè)錯(cuò)誤,表示取消 ctx 的原因。Deadline()
:返回截止時(shí)間和一個(gè) bool 值。Value()
:返回 key 對(duì)應(yīng)的值。
有四個(gè)結(jié)構(gòu)體實(shí)現(xiàn)了這個(gè)接口,分別是:emptyCtx
, cancelCtx
, timerCtx
和 valueCtx
。
其中 emptyCtx
是空類型,暴露了兩個(gè)方法:
func?Background()?Context func?TODO()?Context
一般情況下,會(huì)使用 Background()
作為根 ctx,然后在其基礎(chǔ)上再派生出子 ctx。要是不確定使用哪個(gè) ctx,就使用 TODO()
。
另外三個(gè)也分別暴露了對(duì)應(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 時(shí),要遵循以下四點(diǎn)規(guī)則:
- 不要將 Context 放入結(jié)構(gòu)體,而是應(yīng)該作為第一個(gè)參數(shù)傳入,命名為
ctx
。 - 即使函數(shù)允許,也不要傳入
nil
的 Context。如果不知道用哪種 Context,可以使用context.TODO()
。 - 使用 Context 的 Value 相關(guān)方法只應(yīng)該用于在程序和接口中傳遞和請(qǐng)求相關(guān)的元數(shù)據(jù),不要用它來傳遞一些可選的參數(shù)。
- 相同的 Context 可以傳遞給不同的 goroutine;Context 是并發(fā)安全的。
WithCancel
func?WithCancel(parent?Context)?(ctx?Context,?cancel?CancelFunc)
WithCancel
返回帶有新 Done
通道的父級(jí)副本。當(dāng)調(diào)用返回的 cancel
函數(shù)或關(guān)閉父上下文的 Done
通道時(shí),返回的 ctx
的 Done
通道將關(guān)閉。
取消此上下文會(huì)釋放與其關(guān)聯(lián)的資源,因此在此上下文中運(yùn)行的操作完成后,代碼應(yīng)立即調(diào)用 cancel
。
舉個(gè)例子:
這段代碼演示了如何使用可取消上下文來防止 goroutine 泄漏。在函數(shù)結(jié)束時(shí),由 gen
啟動(dòng)的 goroutine 將返回而不會(huì)泄漏。
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í)的截止日期已經(jīng)早于 d
,則 WithDeadline(parent, d)
在語義上等同于 parent
。
當(dāng)截止時(shí)間到期、調(diào)用返回的取消函數(shù)時(shí)或當(dāng)父上下文的 Done
通道關(guān)閉時(shí),返回的上下文的 Done
通道將關(guān)閉。
取消此上下文會(huì)釋放與其關(guān)聯(lián)的資源,因此在此上下文中運(yùn)行的操作完成后,代碼應(yīng)立即調(diào)用取消。
舉個(gè)例子:
這段代碼傳遞具有截止時(shí)間的上下文,來告訴阻塞函數(shù),它應(yīng)該在到達(dá)截止時(shí)間時(shí)立刻退出。
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))
。
取消此上下文會(huì)釋放與其關(guān)聯(lián)的資源,因此在此上下文中運(yùn)行的操作完成后,代碼應(yīng)立即調(diào)用取消。
舉個(gè)例子:
這段代碼傳遞帶有超時(shí)的上下文,以告訴阻塞函數(shù)應(yīng)在超時(shí)后退出。
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
返回父級(jí)的副本,其中與 key
關(guān)聯(lián)的值為 val
。
其中鍵必須是可比較的,并且不應(yīng)是字符串類型或任何其他內(nèi)置類型,以避免使用上下文的包之間發(fā)生沖突。 WithValue
的用戶應(yīng)該定義自己的鍵類型。
為了避免分配給 interface{}
,上下文鍵通常具有具體的 struct{}
類型?;蛘撸瑢?dǎo)出的上下文鍵變量的靜態(tài)類型應(yīng)該是指針或接口。
舉個(gè)例子:
這段代碼演示了如何將值傳遞到上下文以及如何檢索它(如果存在)。
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)過驗(yàn)證可以執(zhí)行的。如果有不是特別清晰的地方,可以直接去讀官方文檔。
以上就是深入了解Go語言中context的用法的詳細(xì)內(nèi)容,更多關(guān)于Go context的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go語言實(shí)現(xiàn)猜數(shù)字小游戲的方法
這篇文章主要介紹了go語言實(shí)現(xiàn)猜數(shù)字小游戲的方法,實(shí)例分析了Go語言流程判斷與處理的技巧,需要的朋友可以參考下2015-03-03go?mod文件內(nèi)容版本號(hào)簡(jiǎn)單用法詳解
這篇文章主要為大家介紹了go?mod文件內(nèi)容版本號(hào)簡(jiǎn)單用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10如何使用Go語言實(shí)現(xiàn)基于泛型的Jaccard相似度算法
這篇文章主要介紹了如何使用Go語言實(shí)現(xiàn)基于泛型的Jaccard相似度算法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-08-08GoFrame?gredis配置文件及配置方法對(duì)比
這篇文章主要為大家介紹了GoFrame?gredis配置管理中,配置文件及配置方法對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06