Golang中context包使用場(chǎng)景和示例詳解
控制子協(xié)程退出
context包提供了一種機(jī)制,可以在多個(gè)goroutine之間進(jìn)行通信和控制。使用Context包能夠有效地控制程序的并發(fā)性,提高程序的健壯性和性能。
Golang是沒(méi)有辦法讓其他goroutine退出的,goroutine只能自己退出。之所以說(shuō)context包可以控制子協(xié)程退出意思是子協(xié)程可以接收到主協(xié)程發(fā)出的退出信號(hào),然后自己退出??慈缦率纠a:
package main import ( "context" "errors" "sync" ) func request(ctx context.Context, url string) error { result := make(chan int) err := make(chan error) go func() { // 假如isSuccess是請(qǐng)求返回的結(jié)果,成功則通過(guò)result傳遞成功信息,錯(cuò)誤通過(guò)error傳遞錯(cuò)誤信息 isSuccess := true if isSuccess { result <- 1 } else { err <- errors.New("some error happen") } }() select { case <-ctx.Done(): // 其他請(qǐng)求失敗 return ctx.Err() case e := <-err: // 本次請(qǐng)求失敗,返回錯(cuò)誤信息 return e case <-result: // 本此請(qǐng)求成功,不返回錯(cuò)誤信息 return nil } } func main() { ctx, cancel := context.WithCancel(context.Background()) // 調(diào)用接口a err := request(ctx, "https://xxx.com/a") if err != nil { return } wg := sync.WaitGroup{} // 調(diào)用接口b wg.Add(1) go func() { defer wg.Done() err := request(ctx, "https://xxx.com/b") if err != nil { cancel() } }() // 調(diào)用接口c wg.Add(1) go func() { defer wg.Done() err := request(ctx, "https://xxx.com/c") if err != nil { cancel() } }() wg.Wait() }
首先調(diào)用context.WithCancel方法構(gòu)造了一個(gè)Context和返回了一個(gè)cancel函數(shù),其他goroutine調(diào)用的方法都傳入了這個(gè)Context作為第一個(gè)參數(shù),當(dāng)主goroutine想要告訴所有g(shù)oroutine需要退出的時(shí)候,通過(guò)調(diào)用cancel函數(shù)把退出的信息告訴所有的goroutine。所有g(shù)oroutine通過(guò)監(jiān)聽(tīng)ctx.Done返回的channel得到退出信號(hào)然后退出。
超時(shí)控制
例如查詢(xún)數(shù)據(jù)庫(kù)、調(diào)用RPC服務(wù)、調(diào)用HTTP接口等場(chǎng)景,這些操作都是阻塞的,如果一直不返回?cái)?shù)據(jù)的話(huà),會(huì)影響產(chǎn)品的用戶(hù)體驗(yàn)。針對(duì)這些情況的解決方式通常是設(shè)置一個(gè)超時(shí)間,超過(guò)后自動(dòng)取消操作。
使用context包中的WithDeadline和WithTimeout方法可以實(shí)現(xiàn)這個(gè)解決方法。先看如下例子:
package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) // 輸出 "context deadline exceeded" } }
因?yàn)樵O(shè)置的超時(shí)時(shí)間是50毫秒,所以select會(huì)進(jìn)入第二個(gè)case,會(huì)輸出“context deadline exceeded”。
Golang的net/http包發(fā)起http請(qǐng)求的時(shí)候是實(shí)現(xiàn)了超時(shí)控制的,看如下代碼:
package main import ( "context" "io" "log" "net/http" "time" ) func main() { req, err := http.NewRequest(http.MethodGet, "https://www.baidu.com", nil) if err != nil { log.Fatal(err) } // 構(gòu)造一個(gè)超時(shí)間為50毫秒的Context ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() req = req.WithContext(ctx) c := &http.Client{} res, err := c.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() out, err := io.ReadAll(res.Body) if err != nil { log.Fatal(err) } log.Println(string(out)) }
執(zhí)行后會(huì)輸出“context deadline exceeded”,如果將context.WithTimeout方法的timeout參數(shù)調(diào)大一些,就可以看到正常的返回?cái)?shù)據(jù)。
上下文傳遞數(shù)據(jù)
這個(gè)作用在鏈路追蹤中非常重要,鏈路追蹤需要將traceID層層往下傳遞,在服務(wù)間傳遞。
type traceIdKey struct{}{} // 定義固定的Key var TraceIdKey = traceIdKey{} func ServeHTTP(w http.ResponseWriter, req *http.Request){ // 首先從請(qǐng)求中獲取到traceID traceId := getTraceIdFromRequest(req) // 將Key存入Context中 ctx := context.WithValue(req.Context(), TraceIdKey, traceId) // 設(shè)置超時(shí)時(shí)間 ctx = context.WithTimeout(ctx, time.Second) // 攜帶traceId發(fā)起rpc請(qǐng)求 repResp := RequestRPC(ctx, ...) // 攜帶traceId查詢(xún)DB dbResp := RequestDB(ctx, ...) // ... } func RequestRPC(ctx context.Context, ...) interface{} { // 獲取traceid,在調(diào)用rpc時(shí)記錄日志 traceId, _ := ctx.Value(TraceIdKey) // 發(fā)起請(qǐng)求 // ... return }
接收到請(qǐng)求后,通過(guò)req獲取到traceId并記錄到Context中,在調(diào)用其他RPC服務(wù)和查詢(xún)DB時(shí),傳入構(gòu)造的Context。在后續(xù)代碼中,可以通過(guò)Context拿到存入的traceId。
以上就是Golang中context包使用場(chǎng)景和示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang context包使用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)Stack(堆棧)的示例詳解
在計(jì)算機(jī)科學(xué)中,stack(棧)是一種基本的數(shù)據(jù)結(jié)構(gòu),它是一種線(xiàn)性結(jié)構(gòu),具有后進(jìn)先出(Last In First Out)的特點(diǎn)。本文將通過(guò)Golang實(shí)現(xiàn)堆棧,需要的可以參考一下2023-04-04golang連接mysql數(shù)據(jù)庫(kù)操作使用示例
這篇文章主要為大家介紹了golang連接mysql數(shù)據(jù)庫(kù)操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04golang NewRequest/gorequest實(shí)現(xiàn)http請(qǐng)求的示例代碼
本文主要介紹了golang NewRequest/gorequest實(shí)現(xiàn)http請(qǐng)求的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08goland?-sync/atomic原子操作小結(jié)
這篇文章主要介紹了goland?-sync/atomic原子操作,原子操作能夠保證執(zhí)行期間是連續(xù)且不會(huì)被中斷(變量不會(huì)被其他修改,mutex可能存在被其他修改的情況),本文給大家介紹的非常詳細(xì),需要的朋友參考下2022-08-08GO將mysql?中?decimal?數(shù)據(jù)類(lèi)型映射到?protobuf的操作方法
這篇文章主要介紹了go如何優(yōu)雅地將?mysql?中?decimal?數(shù)據(jù)類(lèi)型映射到?protobuf,本文主要展示一下在 protobuf中 float與double的一個(gè)區(qū)別,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09Golang?使用os?庫(kù)的?ReadFile()?讀文件最佳實(shí)踐
這篇文章主要介紹了Golang使用os庫(kù)的ReadFile()讀文件最佳實(shí)踐,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09golang interface指針實(shí)現(xiàn)示例
本文主要介紹了golang interface指針實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08golang interface判斷為空nil的實(shí)現(xiàn)代碼
這篇文章主要介紹了golang interface判斷為空nil的實(shí)現(xiàn)代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04詳解Golang開(kāi)啟http服務(wù)的三種方式
這篇文章主要介紹了詳解Golang開(kāi)啟http服務(wù)的三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06