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

Go语言如何改造getIPSetInfo函数实现HTTP请求并发执行

实现思路

你当前的getIPSetInfo为串行执行逻辑,循环内逐个发起HTTP请求、等待响应、解析结果,所有请求的IO等待时间线性累加,执行效率极低。Go的goroutine是轻量级执行单元,非常适合这类IO密集型并行场景,重构时只要规避并发写竞态、保证结果顺序、补上缺失的错误处理即可。

基础并发实现(无并发数限制)

这个版本会为每个IPSet ID启动一个goroutine发请求,通过sync.WaitGroup等待所有请求完成;预分配固定长度的结果切片,每个goroutine按传入的索引写入对应位置,既避免了并发写切片的竞态问题,也能保证返回结果的顺序和传入的ipsetIds顺序完全一致。

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"sync"
	"text/template"
	// 其他原有依赖包
)

// IpsetInfo、ObjDesc结构体保持原有定义不变即可

func getIPSetInfo(ipsetIds []string) ([]IpsetInfo, error) {
	// 预分配和入参长度一致的结果切片,避免并发append的竞态问题
	result := make([]IpsetInfo, len(ipsetIds))
	var wg sync.WaitGroup
	// 带缓冲的错误通道,收集各goroutine的运行错误
	errChan := make(chan error, len(ipsetIds))

	for i, id := range ipsetIds {
		wg.Add(1)
		// 索引、id作为参数传入goroutine,避免闭包变量捕获导致的数据错误
		go func(idx int, ipsetId string) {
			defer wg.Done()
			url := "https://api-cloud-platform.com/cloudapi/1.0.0/firewallGroups/" + ipsetId
			respCode, body := httpGet(url, map[string]string{
				"Authorization": "Bearer " + accessToken,
			})
			// 校验请求状态,可根据你自己封装的httpGet返回逻辑调整
			if respCode != 200 {
				errChan <- fmt.Errorf("request ipset %s failed, status code: %d", ipsetId, respCode)
				return
			}
			var item IpsetInfo
			if err := json.Unmarshal([]byte(body), &item); err != nil {
				errChan <- fmt.Errorf("parse ipset %s response failed: %w", ipsetId, err)
				return
			}
			// 按索引写入对应位置,无并发冲突
			result[idx] = item
		}(i, id)
	}

	// 所有请求完成后关闭错误通道
	wg.Wait()
	close(errChan)

	// 检查是否有请求失败
	for err := range errChan {
		if err != nil {
			return nil, err
		}
	}

	return result, nil
}

对应的main函数适配错误返回即可,建议用defer处理文件关闭,避免异常分支下的句柄泄漏:

func main() {
	ipsetIds := getIPSetIDs()
	IPSets, err := getIPSetInfo(ipsetIds)
	if err != nil {
		log.Fatalf("get ipset info failed: %v", err)
	}

	tmpl, err := template.ParseFiles("ipsets.template")
	if err != nil {
		log.Fatal(err)
	}
	f, err := os.Create("result.tf")
	if err != nil {
		log.Println("create file: ", err)
		return
	}
	defer f.Close()
	err = tmpl.Execute(f, IPSets)
	if err != nil {
		log.Print("execute: ", err)
		return
	}
}

注:如果你的httpGet封装本身会返回error类型,可以直接替换上述代码里的状态码判断逻辑。

进阶:添加并发数限制

如果ipsetIds列表很长(比如上百个),无限制并发请求很可能触发云平台API限流,甚至耗尽本地端口资源。可以通过带缓冲的channel实现简单的并发闸门,比如限制最多同时发起10个请求,只需要修改getIPSetInfo的逻辑即可:

func getIPSetInfo(ipsetIds []string, maxConcurrent int) ([]IpsetInfo, error) {
	result := make([]IpsetInfo, len(ipsetIds))
	var wg sync.WaitGroup
	errChan := make(chan error, len(ipsetIds))
	// 缓冲区大小即为最大并发数
	semaphore := make(chan struct{}, maxConcurrent)

	for i, id := range ipsetIds {
		wg.Add(1)
		go func(idx int, ipsetId string) {
			defer wg.Done()
			// 拿到令牌才发起请求,令牌耗尽时阻塞等待其他请求释放
			semaphore <- struct{}{}
			defer func() { <-semaphore }()

			// 下方请求、解析逻辑和基础版本完全一致
			url := "https://api-cloud-platform.com/cloudapi/1.0.0/firewallGroups/" + ipsetId
			respCode, body := httpGet(url, map[string]string{
				"Authorization": "Bearer " + accessToken,
			})
			if respCode != 200 {
				errChan <- fmt.Errorf("request ipset %s failed, status code: %d", ipsetId, respCode)
				return
			}
			var item IpsetInfo
			if err := json.Unmarshal([]byte(body), &item); err != nil {
				errChan <- fmt.Errorf("parse ipset %s response failed: %w", ipsetId, err)
				return
			}
			result[idx] = item
		}(i, id)
	}

	wg.Wait()
	close(errChan)
	close(semaphore)

	for err := range errChan {
		if err != nil {
			return nil, err
		}
	}

	return result, nil
}

调用时指定并发数即可,例如IPSets, err := getIPSetInfo(ipsetIds, 10)即代表最多同时跑10个请求。

注意事项
  • 禁止在goroutine内直接使用循环变量i、id,必须作为参数传入goroutine,否则会因为闭包变量捕获,导致所有goroutine拿到最后一次循环的值,引发数据错误
  • 请复用全局http.Client实例,不要每个请求新建Client,标准库http.Client本身是并发安全的,复用可以减少连接开销
  • 如果accessToken会在运行时动态更新,需要加读写锁保护,避免并发读写出错

内容的提问来源于stack exchange,提问作者Vladimir Fomin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:01:41