Problem

情况

我目前有一个gin handler函数,它使用相同的上下文在三个不同的goroutine中运行三个不同的查询.有一个err组("golang.org/x/sync/errgroup")使用这个共享上下文,处理程序在返回之前等待err组.

客观的

我试图实现的行为是,在其中一个goroutine完成后,剩余goroutine上应该有一个超时,但是如果gin请求被取消(连接关闭),这个上下文也应该被取消,这意味着必须使用gin的ctx.Request.Context().

Potential Solutions

目前的执行情况

目前,我有一个超时的上下文传递给一个errgroup,但这只是强制所有goroutine超时.

timeoutCtx := context.WithTimeout(context.Background(), 10*time.Second)
g, err := errgroup.WithContext(timeoutCtx)

g.Go(func1)
g.Go(func2)
g.Go(func3)

err = g.Wait()

需要使用gin请求上下文,以便在连接关闭且请求取消时,goroutines也将停止.

// ctx *gin.Context
g, err := errgroup.WithContext(ctx.Request.Context())

g.Go(func1)
g.Go(func2)
g.Go(func3)

err = g.Wait()

使用通道实现超时

Source

package main

import (
    "fmt"
    "time"
)

func main() {

    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()

    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        fmt.Println("timeout 1")
    }

    c2 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "result 2"
    }()
    select {
    case res := <-c2:
        fmt.Println(res)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout 2")
    }
}

结合通道和请求上下文

这个解决方案很接近,但不是很优雅或完整.

cQueryDone := make(chan bool)

g, err := errgroup.WithContext(ctx.Request.Context())

g.Go(func1)
g.Go(func2)
g.Go(func3)

// assumes func1 func2 and func3 all have cQueryDone <- true

if <-cQueryDone {
    select {
        case <-cQueryDone:
            select {
                case <-cQueryDone:
                    // ctx.JSON
                    // return
                case <-time.After(1*time.Second):
                    // ctx.JSON
                    // return
            }
        case <-time.After(3*time.Second):
            // ctx.JSON
            // return
    }
}

err = g.Wait()

有没有更好、更惯用的方法在Go中实现这种行为?

推荐答案

请注意,context.WithTimeout():

  • 可以包装任何上下文(不仅仅是context.Background()个)
  • 还返回一个cancel函数

您可以在ctx.Request.Context()上方添加一个超时,并在任何查询完成时调用cancel:

timeoutCtx, cancel := context.WithTimeout(ctx.Request.Context())

g, err := errgroup.WithContext(timeoutCtx)

g.Go( func1(cancel) ) // pass the cancel callback to each query some way or another
g.Go( func2(cancel) ) // you prabably want to also pass timeoutCtx
g.Go( func3(cancel) )

g.Wait()

根据您的 comments :还有context.WithCancel()个,您可以在延迟后拨打cancel

childCtx, cancel := context.WithCancel(ctx.Request.Context())

g, err := errgroup.WithContext(childCtx)

hammerTime := func(){
    <-time.After(1*time.Second)
    cancel()
}

g.Go( func1(hammerTime) ) // funcXX should have access to hammerTime
g.Go( func2(hammerTime) )
g.Go( func3(hammerTime) )

g.Wait()

Go相关问答推荐

Term~T中的类型不能是类型参数,但可以是引用类型参数的切片

如何使用Gorilla WebSockets实现Http.Hijacker&;alexedwards/scs/v2

如何在GoFr中为生产和本地环境设置不同的配置?

如何使用工作区方法扩展克隆的Golang库

如何为循环扫描的bufio scanner 设置超时?

优化方式中所有可能组合的字符串相似度

如何在正则表达式中使整个单词可选?

我们如何保证取消的上下文会导致 goroutine 终止?

是否需要手动调用rand.Seed?

最长连续重复的字符golang

如何使用 go-playground/validator 编写 snake case 绑定标签?

我相信我正确地在 sRGB 和线性 RGB 之间进行了转换,那么为什么深色的结果看起来更糟呢?

为什么 0 big.Int 的 .Bytes() 值是空切片?

为什么 x/net/html Token().Attr 上的 len 在此处为空切片返回非零值?

如何正确为 Go 中的值设置多种类型?

将 CSVExport 函数传递给处理程序 Gin

在 golang 中联合一个接口和类型

什么是无效字符实体 &ccb

如何在 docker 文件中安装 golang 包?

Go Flag 用法 描述 包含 Word 值