网站建设源码下载,网站开发人员 组织架构,WordPress的MySQL宕,个人工作室注册流程及费用目录
基于现有的 context 创建新的 context
现有创建方法的问题
Go 1.21 中的 context.WithoutCancel 函数
Go 版本低于 1.21 该怎么办#xff1f; 在 Golang 中#xff0c;context 包提供了创建和管理上下文的功能。当需要基于现有的 context.Context 创建新的 context …目录
基于现有的 context 创建新的 context
现有创建方法的问题
Go 1.21 中的 context.WithoutCancel 函数
Go 版本低于 1.21 该怎么办 在 Golang 中context 包提供了创建和管理上下文的功能。当需要基于现有的 context.Context 创建新的 context 时通常是为了添加额外的控制信息或为了满足特定的生命周期需求。
基于现有的 context 创建新的 context
可以基于现有的 context.Context 创建一个新的 context对应的函数有 context.WithCancel、context.WithDeadline、context.WithTimeout 或 context.WithValue。这些函数会返回一个新的 context.Context 实例继承了原来 context 的行为并添加了新的行为或值。使用 context.WithValue 函数创建的简单示例代码如下
package mainimport contextfunc main() {// 假设已经有了一个context ctxctx : context.Background()// 可以通过context.WithValue创建一个新的contextkey : myKeyvalue : myValuenewCtx : context.WithValue(ctx, key, value)// 现在newCtx包含了原始ctx的所有数据加上新添加的键值对
}
使用 context.WithCancel 函数创建简单示例代码如下
package mainimport contextfunc main() {// 假设已经有了一个context ctxctx : context.Background()// 创建一个可取消的contextnewCtx, cancel : context.WithCancel(ctx)// 当完成了newCtx的使用可以调用cancel来取消它// 这将释放与该context相关的资源defer cancel()
}
现有创建方法的问题
先说一个使用场景一个接口处理完基本的任务之后后续一些处理的任务放使用新开的 Goroutine 来处理这时候会基于当前的 context 创建一个 context可以使用上面提到的方法来创建 给 Goroutine 使用也不需要控制 Goroutine 的超时时间。
这种场景下Goroutine 的声明周期一般都会比这个接口的生命周期长这就会出现一个问题——当前接口请求所属的 Goroutine 退出后会导致 context 被 cancel进而导致新开的 Goroutine 中的 context 跟着被 cancel 从而导致程序异常。看一个示例
package mainimport (bytescontexterrorsfmtionet/httpgithub.com/gin-gonic/gin
)func main() {r : gin.New()r.GET(/test, func(c *gin.Context) {// 父 context有使用取消功能ctx, cancel : context.WithCancel(c)defer cancel()// 创建子 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导致子 context 也被 cancel从而导致程序异常。因此需要一种既能继承父 context 所有的 value 信息又能去除父级 context 的 cancel 机制的创建函数。
Go 1.21 中的 context.WithoutCancel 函数
这种函数该如何实现呢其实 Golang 从 1.21 版本开始为我们提供了这样一个函数就是 context 包中的 WithoutCancel 函数。源代码如下
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
}
原理其实很简单主要功能是创建一个新的 context 类型继承了父 context 的所有属性但重写了 Deadline、Done、Err、Value 几个方法当父 context 被取消时不会触发任何操作。
Go 版本低于 1.21 该怎么办
如果 Go 版本低于 1.21 其实也很好办按照 Go 1.21 中的实现方式自己实现一个就可以了代码可以进一步精简示例代码如下
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
}
使用自己实现的这个版本再跑一下之前的示例代码如下
package mainimport (bytescontexterrorsfmtionet/httptimegithub.com/gin-gonic/gin
)func main() {r : gin.New()r.GET(/test, func(c *gin.Context) {// 父 context有使用取消功能ctx, cancel : context.WithCancel(c)defer cancel()// 创建子 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() errorValue(key any) any
}
运行代码在浏览器中访问 http://127.0.0.1:8080/test发现不再报父 context 被 cancel 导致的报错了。