目錄
基于現(xiàn)有的 context 創(chuàng)建新的 context
現(xiàn)有創(chuàng)建方法的問題
Go 1.21 中的 context.WithoutCancel 函數(shù)
Go 版本低于 1.21 該怎么辦?
在 Golang 中,context 包提供了創(chuàng)建和管理上下文的功能。當(dāng)需要基于現(xiàn)有的 context.Context 創(chuàng)建新的 context 時,通常是為了添加額外的控制信息或為了滿足特定的生命周期需求。
基于現(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 實例,繼承了原來 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
}
運行代碼,在瀏覽器中訪問?http://127.0.0.1:8080/test,控制臺會打印如下錯誤信息:
goroutine...
Post "http://xxx.luduoxin.com/xxx": context canceled
可以看出,因為父級 context 被 cancel,導(dǎo)致子 context 也被 cancel,從而導(dǎo)致程序異常。因此,需要一種既能繼承父 context 所有的 value 信息,又能去除父級 context 的 cancel 機制的創(chuàng)建函數(shù)。
Go 1.21 中的 context.WithoutCancel 函數(shù)
這種函數(shù)該如何實現(xiàn)呢?其實 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"
}
原理其實很簡單,主要功能是創(chuàng)建一個新的 context 類型,繼承了父 context 的所有屬性,但重寫了 Deadline、Done、Err、Value 幾個方法,當(dāng)父 context 被取消時不會觸發(fā)任何操作。
Go 版本低于 1.21 該怎么辦?
如果 Go 版本低于 1.21 其實也很好辦,按照 Go 1.21 中的實現(xiàn)方式自己實現(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
}
使用自己實現(xiàn)的這個版本再跑一下之前的示例,代碼如下:文章來源:http://www.zghlxwxcb.cn/news/detail-797759.html
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
}
運行代碼,在瀏覽器中訪問 http://127.0.0.1:8080/test,發(fā)現(xiàn)不再報父 context 被 cancel 導(dǎo)致的報錯了。文章來源地址http://www.zghlxwxcb.cn/news/detail-797759.html
到了這里,關(guān)于Golang 如何基于現(xiàn)有的 context 創(chuàng)建新的 context?的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!