本文介绍使用 goroutine 池(生产者-消费者模式)限制 http 请求并发数的最简实践,以 16 为上限安全处理 10k+ url,避免资源耗尽与服务拒绝。
本文介绍使用 goroutine 池(生产者-消费者模式)限制 http 请求并发数的最简实践,以 16 为上限安全处理 10k+ url,避免资源耗尽与服务拒绝。
在批量处理大量 HTTP 请求(如解析 CSV 中的 10,000 个 URL)时,无节制地启动 goroutine 会导致内存暴涨、文件描述符耗尽、目标服务器限流甚至本地崩溃。Go 本身不提供内置的并发数限制机制,但可通过 通道(channel)+ 固定数量工作协程 + sync.WaitGroup 构建轻量级并发控制器——即经典的生产者-消费者模型。
核心思路是:
✅ 创建一个带缓冲或无缓冲的字符串通道作为任务队列;
✅ 启动固定数量(如 16 个)的工作 goroutine,持续从通道中读取 URL 并执行请求;
✅ 主 goroutine 作为生产者,将所有 URL 发送到通道;
✅ 使用 sync.WaitGroup 确保所有工作协程完成后再退出程序。
以下是完整、可运行的实现(已适配 Go 模块与错误处理规范):
package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"sync"
"github.com/parnurzeal/gorequest"
)
const workersCount = 16 // 并发上限:16 个 HTTP 请求同时进行
func getUrlWorker(urlChan <-chan string, wg *sync.WaitGroup) {
defer wg.Done()
for url := range urlChan {
request := gorequest.New()
resp, body, errs := request.Get(url).End()
if len(errs) > 0 {
log.Printf("Failed to fetch %s: %v", url, errs[0])
continue
}
// 可选:处理响应
// fmt.Printf("Fetched %s: %d bytes\n", url, len(body))
_ = resp
_ = body
}
}
func main() {
csvfile, err := os.Open("urls.csv")
if err != nil {
log.Fatal("Failed to open CSV:", err)
}
defer csvfile.Close()
reader := csv.NewReader(csvfile)
reader.FieldsPerRecord = -1 // 自适应字段数
rawCSVdata, err := reader.ReadAll()
if err != nil {
log.Fatal("Failed to read CSV:", err)
}
// 创建无缓冲通道(也可用带缓冲通道提升吞吐,但非必需)
urlChan := make(chan string, 100) // 缓冲 100 提升生产者效率,防阻塞
var wg sync.WaitGroup
wg.Add(workersCount)
// 启动固定数量工作协程
for i := 0; i < workersCount; i++ {
go getUrlWorker(urlChan, &wg)
}
// 生产者:推送所有 URL 到通道
for _, row := range rawCSVdata {
if len(row) < 2 {
continue // 跳过格式异常行
}
urlChan <- row[1] // 假设 URL 在第二列(索引 1)
}
close(urlChan) // 关闭通道,通知消费者不再有新任务
// 等待所有工作协程完成
wg.Wait()
fmt.Println("All requests completed.")
}⚠️ 关键注意事项:
- 务必关闭通道:close(urlChan) 是必须步骤,否则消费者会永久阻塞在 range urlChan;
- 避免 goroutine 泄漏:每个工作协程必须能正常退出(通道关闭后 range 自动结束);
- 错误处理不可省略:示例中增加了日志输出,实际项目建议统一错误收集或重试策略;
- 依赖管理:确保已执行 go get github.com/parnurzeal/gorequest,或考虑升级至更现代的 net/http + context 实现(gorequest 已归档,生产环境推荐原生 HTTP 客户端);
- 资源复用优化:高频请求建议复用 http.Client(设置 Transport.MaxIdleConnsPerHost),而非每次新建 gorequest.New()。
该方案零第三方依赖(除 gorequest 外)、逻辑清晰、易于理解与维护,是 Go 中控制并发的「最小可行解」。如需动态调优或集成超时/取消/限速,可在通道层之上叠加 context.Context 或封装为可复用的 WorkerPool 结构体。


















