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()
使用通道实现超时
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中实现这种行为?