Golang 如何基于现有的 context 创建新的 context?

这篇具有很好参考价值的文章主要介绍了Golang 如何基于现有的 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 main

import "context"

func main() {
	// 假设已经有了一个context ctx
	ctx := context.Background()
	// 可以通过context.WithValue创建一个新的context
	key := "myKey"
	value := "myValue"
	newCtx := context.WithValue(ctx, key, value)
	// 现在newCtx包含了原始ctx的所有数据,加上新添加的键值对
}

使用 context.WithCancel 函数创建,简单示例代码如下:

package main

import "context"

func main() {
    // 假设已经有了一个context ctx
    ctx := context.Background()
    // 创建一个可取消的context
    newCtx, cancel := context.WithCancel(ctx)
    // 当完成了newCtx的使用,可以调用cancel来取消它
    // 这将释放与该context相关的资源
    defer cancel()
}

现有创建方法的问题

先说一个使用场景:一个接口处理完基本的任务之后,后续一些处理的任务放使用新开的 Goroutine 来处理,这时候会基于当前的 context 创建一个 context(可以使用上面提到的方法来创建) 给 Goroutine 使用,也不需要控制 Goroutine 的超时时间。

这种场景下,Goroutine 的声明周期一般都会比这个接口的生命周期长,这就会出现一个问题——当前接口请求所属的 Goroutine 退出后会导致 context 被 cancel,进而导致新开的 Goroutine 中的 context 跟着被 cancel, 从而导致程序异常。看一个示例:

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()

       // 创建子 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 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()

       // 创建子 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,发现不再报父 context 被 cancel 导致的报错了。文章来源地址https://www.toymoban.com/news/detail-797759.html

到了这里,关于Golang 如何基于现有的 context 创建新的 context?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • git 如何创建新的远程分支,并将本地代码 push 到新的分支

    1、可以通过git branch -r 命令查看远端库的分支情况 2、从已有的分支创建新的分支(如从master分支),创建一个 duanjiwang 分支 但此时并没有在远程仓库上创建分支 如图所示 还是只有一个 master 分支 3、建立本地到远端仓库的链接 --这样代码才能提交上去 使用命令行 4、git 强制提交

    2024年02月12日
    浏览(61)
  • Eplan2022 复制已有的宏文件生成新的原理图宏文件

    下图所示为wago的787-722稳压电源,我们可以从官网下载到相应的eplan宏文件并导入数据源库。但是能下载到eplan宏文件的只是少部分公司的部件。那么没有宏文件的部件该怎么办? 接下来以明纬开关电源 NDR-120-24为例,创建一个宏文件。选择【主数据】➡【管理】,进入部件管

    2024年02月03日
    浏览(119)
  • git基于原有的分支拉取(创建)一个新的分支

    git checkout -b newbranch origin/oldbranchname newbranch:你要创建的分支的名子 oldbranchname:原来的分支(你要基于的分支) git push --set-upstream origin newbranch newbranch:你刚刚创建的新分支的名字 git push origin newbranch 或者直接 git push newbranch:你刚刚创建的新分支的名字

    2024年02月15日
    浏览(52)
  • idea 2023.3.2版本如何创建新的maven项目

    1.首先点击new--project  2.填写相关项目名,存放的地址等  3.创建后的效果 4.添加maven依赖,若依赖一直下载不下来,注意在setting中配置一下maven(根据自己的情况配置)以及jdk等     根据自己的实际安装地址,进行maven相关配置 配置jdk  配置jre 5.创建下的maven项目没有Resour

    2024年02月15日
    浏览(40)
  • react后端开发:如何根据特定ID创建新的用户信息?

    以音乐app开发为例,我们想要在想要创建新的唱片库,就需要使用Post连接服务器端新建唱片ID,并在该ID处插入唱片信息。怎么做呢? 使用create同时创建id和唱片信息 不过在这之前,我们一般先需要进行判断,新写入的唱片是否存在,比如某用户已经上传了周杰伦的青花瓷,

    2024年01月23日
    浏览(55)
  • golang中context详解

    编码中遇到上下文信息传递,并发信息取消等,记录下在go语言中context包的使用。 在Go语言中,context包提供了一种在程序中传递截止日期、取消信号、请求范围数据和其他元数据的方式。context包的核心类型是Context接口,它定义了在执行上下文中传递的方法。Context接口的主要

    2024年01月21日
    浏览(33)
  • Golang:浅析Context包

    在golang官方文档context package - context - Go Packages中是这样介绍context包的: 在context包中定义了context类型来在不同的Goroutine 之间传递上下文,携带截止时间、取消信号以及携带上下文的系统参数(k-v)的类型。对服务器的传入请求应该创建上下文,对服务器的传出调用应该接受上

    2024年02月06日
    浏览(43)
  • golang Context应用举例

      golang标准库里Context实际上是一个接口(即一种编程规范、 一种约定)。   通过查看源码里的注释,我们得到如下约定: Done()函数返回一个只读管道,且管道里不存放任何元素(struct{}),所以用这个管道就是为了实现阻塞 Deadline()用来记录到期时间,以及是否到期。 Err()用来

    2024年02月08日
    浏览(36)
  • golang之context实用记录

    简言 WithCancel()函数接受一个 Context 并返回其子Context和取消函数cancel 新创建协程中传入子Context做参数,且需监控子Context的Done通道,若收到消息,则退出 需要新协程结束时,在外面调用 cancel 函数,即会往子Context的Done通道发送消息 注意:当 父Context的 Done() 关闭的时候,子

    2024年02月09日
    浏览(33)
  • 【golang】Context超时控制与原理

    在Go语言圈子中流行着一句话: Never start a goroutine without knowing how it will stop。 翻译:如果你不知道协程如何退出,就不要使用它。 在创建协程时,我们可能还会再创建一些别的子协程,那么这些协程的退出就成了问题。在Go1.7之后,Go官方引入了Context来实现协程的退出。不仅

    2024年01月22日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包