詳解Go如何基于現(xiàn)有的context創(chuàng)建新的context
在 Golang 中,context 包提供了創(chuàng)建和管理上下文的功能。當(dāng)需要基于現(xiàn)有的 context.Context 創(chuàng)建新的 context 時,通常是為了添加額外的控制信息或?yàn)榱藵M足特定的生命周期需求。
基于現(xiàn)有的 context 創(chuàng)建新的 context
可以基于現(xiàn)有的 context.Context 創(chuàng)建一個新的 context,對應(yīng)的函數(shù)有 context.WithCancel、context.WithDeadline、context.WithTimeout 或 context.WithValue。這些函數(shù)會返回一個新的 context.Context 實(shí)例,繼承了原來 context 的行為,并添加了新的行為或值。使用 context.WithValue 函數(shù)創(chuàng)建的簡單示例代碼如下:
package main import "context" func main() { // 假設(shè)已經(jīng)有了一個context ctx ctx := context.Background() // 可以通過context.WithValue創(chuàng)建一個新的context key := "myKey" value := "myValue" newCtx := context.WithValue(ctx, key, value) // 現(xiàn)在newCtx包含了原始ctx的所有數(shù)據(jù),加上新添加的鍵值對 }
使用 context.WithCancel 函數(shù)創(chuàng)建,簡單示例代碼如下:
package main import "context" func main() { // 假設(shè)已經(jīng)有了一個context ctx ctx := context.Background() // 創(chuàng)建一個可取消的context newCtx, cancel := context.WithCancel(ctx) // 當(dāng)完成了newCtx的使用,可以調(diào)用cancel來取消它 // 這將釋放與該context相關(guān)的資源 defer cancel() }
現(xiàn)有創(chuàng)建方法的問題
先說一個使用場景:一個接口處理完基本的任務(wù)之后,后續(xù)一些處理的任務(wù)放使用新開的 Goroutine 來處理,這時候會基于當(dāng)前的 context 創(chuàng)建一個 context(可以使用上面提到的方法來創(chuàng)建) 給 Goroutine 使用,也不需要控制 Goroutine 的超時時間。
這種場景下,Goroutine 的聲明周期一般都會比這個接口的生命周期長,這就會出現(xiàn)一個問題——當(dāng)前接口請求所屬的 Goroutine 退出后會導(dǎo)致 context 被 cancel,進(jìn)而導(dǎo)致新開的 Goroutine 中的 context 跟著被 cancel, 從而導(dǎo)致程序異常。看一個示例:
package main import ( "bytes" "context" "errors" "fmt" "io" "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.New() r.GET("/test", func(c *gin.Context) { // 父 context,有使用取消功能 ctx, cancel := context.WithCancel(c) defer cancel() // 創(chuàng)建子 context 給新開的 Goroutine 使用 ctxCopy, _ := context.WithCancel(ctx) go func() { err := TestPost(ctxCopy) fmt.Println(err) }() }) r.Run(":8080") } func TestPost(ctx context.Context) error { fmt.Println("goroutine...") buffer := bytes.NewBuffer([]byte(`{"xxx":"xxx"}`)) request, err := http.NewRequest("POST", "http://xxx.luduoxin.com/xxx", buffer) if err != nil { return err } request.Header.Set("Content-Type", "application/json") client := http.Client{} rsp, err := client.Do(request.WithContext(ctx)) if err != nil { return err } defer func() { _ = rsp.Body.Close() }() if rsp.StatusCode != http.StatusOK { return errors.New("response exception") } _, err = io.ReadAll(rsp.Body) if err != nil { return err } return nil }
運(yùn)行代碼,在瀏覽器中訪問 http://127.0.0.1:8080/test,控制臺會打印如下錯誤信息:
goroutine...
Post "http://xxx.luduoxin.com/xxx": context canceled
可以看出,因?yàn)楦讣?context 被 cancel,導(dǎo)致子 context 也被 cancel,從而導(dǎo)致程序異常。因此,需要一種既能繼承父 context 所有的 value 信息,又能去除父級 context 的 cancel 機(jī)制的創(chuàng)建函數(shù)。
Go 1.21 中的 context.WithoutCancel 函數(shù)
這種函數(shù)該如何實(shí)現(xiàn)呢?其實(shí) Golang 從 1.21 版本開始為我們提供了這樣一個函數(shù),就是 context 包中的 WithoutCancel 函數(shù)。源代碼如下:
func WithoutCancel(parent Context) Context { if parent == nil { panic("cannot create context from nil parent") } return withoutCancelCtx{parent} } type withoutCancelCtx struct { c Context } func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) { return } func (withoutCancelCtx) Done() <-chan struct{} { return nil } func (withoutCancelCtx) Err() error { return nil } func (c withoutCancelCtx) Value(key any) any { return value(c, key) } func (c withoutCancelCtx) String() string { return contextName(c.c) + ".WithoutCancel" }
原理其實(shí)很簡單,主要功能是創(chuàng)建一個新的 context 類型,繼承了父 context 的所有屬性,但重寫了 Deadline、Done、Err、Value 幾個方法,當(dāng)父 context 被取消時不會觸發(fā)任何操作。
Go 版本低于 1.21 該怎么辦
如果 Go 版本低于 1.21 其實(shí)也很好辦,按照 Go 1.21 中的實(shí)現(xiàn)方式自己實(shí)現(xiàn)一個就可以了,代碼可以進(jìn)一步精簡,示例代碼如下:
func WithoutCancel(parent Context) Context { if parent == nil { panic("cannot create context from nil parent") } return withoutCancelCtx{parent} } type withoutCancelCtx struct { context.Context } func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) { return } func (withoutCancelCtx) Done() <-chan struct{} { return nil } func (withoutCancelCtx) Err() error { return nil }
使用自己實(shí)現(xiàn)的這個版本再跑一下之前的示例,代碼如下:
package main import ( "bytes" "context" "errors" "fmt" "io" "net/http" "time" "github.com/gin-gonic/gin" ) func main() { r := gin.New() r.GET("/test", func(c *gin.Context) { // 父 context,有使用取消功能 ctx, cancel := context.WithCancel(c) defer cancel() // 創(chuàng)建子 context 給新開的 Goroutine 使用 ctxCopy := WithoutCancel(ctx) go func() { err := TestPost(ctxCopy) fmt.Println(err) }() }) r.Run(":8080") } func WithoutCancel(parent Context) Context { if parent == nil { panic("cannot create context from nil parent") } return withoutCancelCtx{parent} } type withoutCancelCtx struct { context.Context } func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) { return } func (withoutCancelCtx) Done() <-chan struct{} { return nil } func (withoutCancelCtx) Err() error { return nil } func TestPost(ctx context.Context) error { fmt.Println("goroutine...") buffer := bytes.NewBuffer([]byte(`{"xxx":"xxx"}`)) request, err := http.NewRequest("POST", "http://xxx.luduoxin.com/xxx", buffer) if err != nil { return err } request.Header.Set("Content-Type", "application/json") client := http.Client{} rsp, err := client.Do(request.WithContext(ctx)) if err != nil { return err } defer func() { _ = rsp.Body.Close() }() if rsp.StatusCode != http.StatusOK { return errors.New("response exception") } _, err = io.ReadAll(rsp.Body) if err != nil { return err } return nil } type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any }
運(yùn)行代碼,在瀏覽器中訪問 http://127.0.0.1:8080/test,發(fā)現(xiàn)不再報(bào)父 context 被 cancel 導(dǎo)致的報(bào)錯了。
以上就是詳解Go如何基于現(xiàn)有的context創(chuàng)建新的context的詳細(xì)內(nèi)容,更多關(guān)于Go context的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang實(shí)現(xiàn)Directional Channel(定向通道)
這篇文章主要介紹了Golang實(shí)現(xiàn)Directional Channel(定向通道),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Go語言調(diào)用ffmpeg-api實(shí)現(xiàn)音頻重采樣
最近對golang處理音視頻很感興趣,對golang音視頻常用庫goav進(jìn)行了一番研究。自己寫了一個wav轉(zhuǎn)采樣率的功能。給大家分享一下,中間遇到了不少坑,解決的過程中還是蠻有意思的,希望大家能喜歡2022-12-12Go高效率開發(fā)Web參數(shù)校驗(yàn)三種方式實(shí)例
這篇文章主要介紹了Go高效率開發(fā)Web參數(shù)校驗(yàn)三種方式實(shí)例,需要的朋友可以參考下2022-11-11Gin+Gorm實(shí)現(xiàn)增刪改查的示例代碼
本文介紹了如何使用Gin和Gorm框架實(shí)現(xiàn)一個簡單的增刪改查(CRUD)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12