You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:20:37