Go语言协程协调方法咨询:HTTP双资源请求场景实现
Hey there! 你想要在Go里实现类似Node.js Promise.all()的效果来协调多个goroutine(比如并行获取两个HTTP资源),我结合你的代码示例,给你整理几种实用的解决方案,覆盖不同场景需求:
方案1:保持结果顺序的通道方案
如果需要让结果和你启动请求的顺序一一对应(比如第一个请求的结果存在第一个位置),可以给每个请求的结果带上索引,通过通道收集后归位。这个方案还能处理请求失败的情况:
package main import ( "fmt" "time" ) // 定义结果结构体,包含请求索引、返回值和错误信息 type RequestResult struct { Index int Value bool Err error } // 模拟HTTP请求,这里用time.Sleep模拟耗时,可替换为实际的http.Get等逻辑 func simulateRequest(index int, delayMs time.Duration, returnVal bool) RequestResult { time.Sleep(delayMs * time.Millisecond) // 模拟请求失败的情况,比如超时 if delayMs == 2000 { return RequestResult{ Index: index, Value: false, Err: fmt.Errorf("request %d timed out after %dms", index, delayMs), } } return RequestResult{ Index: index, Value: returnVal, Err: nil, } } func main() { const totalRequests = 2 results := make([]RequestResult, totalRequests) resultChan := make(chan RequestResult, totalRequests) // 启动第一个请求goroutine go func() { res := simulateRequest(0, 1000, true) resultChan <- res }() // 启动第二个请求goroutine go func() { res := simulateRequest(1, 0, false) resultChan <- res }() // 收集所有结果,按索引归位 for i := 0; i < totalRequests; i++ { res := <-resultChan results[res.Index] = res } // 处理最终结果 for idx, res := range results { if res.Err != nil { fmt.Printf("Request %d failed: %v\n", idx, res.Err) continue } fmt.Printf("Request %d succeeded, result: %v\n", idx, res.Value) } }
这个方案的好处是能严格保持请求和结果的对应关系,即使某个请求先完成,也不会打乱顺序。
方案2:简洁版 - sync.WaitGroup + 通道
如果不需要关心结果顺序,只需要等待所有请求完成并收集结果,用sync.WaitGroup配合通道会更简洁:
package main import ( "fmt" "sync" "time" ) type SimpleResult struct { Value bool Err error } func simulateRequest(wg *sync.WaitGroup, resultChan chan<- SimpleResult, delayMs time.Duration, returnVal bool) { defer wg.Done() // 标记当前goroutine完成 time.Sleep(delayMs * time.Millisecond) if delayMs == 1500 { resultChan <- SimpleResult{Value: false, Err: fmt.Errorf("request failed unexpectedly")} return } resultChan <- SimpleResult{Value: returnVal, Err: nil} } func main() { var wg sync.WaitGroup resultChan := make(chan SimpleResult, 2) // 标记需要等待的goroutine数量 wg.Add(2) go simulateRequest(&wg, resultChan, 1000, true) go simulateRequest(&wg, resultChan, 0, false) // 启动一个goroutine,等待所有请求完成后关闭通道(避免死锁) go func() { wg.Wait() close(resultChan) }() // 遍历通道收集所有结果 for res := range resultChan { if res.Err != nil { fmt.Println("Request error:", res.Err) continue } fmt.Println("Request result:", res.Value) } }
这里WaitGroup负责等待所有goroutine执行完毕,之后关闭通道,我们通过遍历通道就能拿到所有结果。
方案3:生产环境推荐 - errgroup.Group + 上下文
如果需要实现“任一请求失败则取消其他请求”的逻辑(类似Promise.all()中一个Promise reject就整体reject的行为),可以用errgroup.Group配合上下文,这在HTTP请求场景中非常实用,能避免不必要的资源浪费:
package main import ( "context" "errors" "fmt" "golang.org/x/sync/errgroup" "time" ) func fetchResource(ctx context.Context, delayMs time.Duration, returnVal bool) (bool, error) { select { case <-ctx.Done(): // 上下文被取消,说明其他请求出错了,直接返回取消错误 return false, ctx.Err() case <-time.After(delayMs * time.Millisecond): // 模拟请求失败 if delayMs == 2000 { return false, errors.New("resource fetch timed out") } return returnVal, nil } } func main() { // 创建带上下文的errgroup,任一goroutine返回错误会取消上下文 g, ctx := errgroup.WithContext(context.Background()) var res1, res2 bool // 启动第一个资源请求 g.Go(func() error { var err error res1, err = fetchResource(ctx, 1000, true) return err }) // 启动第二个资源请求 g.Go(func() error { var err error res2, err = fetchResource(ctx, 0, false) return err }) // 等待所有请求完成,或任一请求出错 if err := g.Wait(); err != nil { fmt.Println("Request failed:", err) return } fmt.Printf("Resource 1 result: %v, Resource 2 result: %v\n", res1, res2) }
这个方案中,只要有一个请求返回错误,errgroup会立即取消上下文,其他正在执行的goroutine可以通过ctx.Done()感知到并提前退出,非常适合生产环境中处理并行HTTP请求。
内容的提问来源于stack exchange,提问作者Hume
相关产品推荐
相关产品推荐

