
本文介绍一种基于生产者-消费者模型的轻量级方案,使用固定数量的工作协程(如 16 个)从通道中消费 url 并发起 http 请求,从而精确控制并发度,避免资源耗尽或服务端限流。
本文介绍一种基于生产者-消费者模型的轻量级方案,使用固定数量的工作协程(如 16 个)从通道中消费 url 并发起 http 请求,从而精确控制并发度,避免资源耗尽或服务端限流。
在处理大量 HTTP 请求(例如解析 10,000 条 CSV 中的 URL)时,若不加限制地启动 goroutine(如原始代码中每行 go getUrl(...)),极易导致系统文件描述符耗尽、内存飙升,或触发目标服务器的速率限制与拒绝响应。最简洁、可靠且符合 Go 语言惯用法的解决方案是采用固定工作池(Worker Pool) —— 即经典的生产者-消费者模式。
其核心思想是:
✅ 一个生产者:主线程按序读取 CSV 数据,并将 URL 发送至无缓冲通道;
✅ N 个消费者(worker):启动固定数量(如 16)的 goroutine,持续从通道接收 URL 并执行请求;
✅ 同步保障:使用 sync.WaitGroup 确保所有 worker 完成后主程序才退出。
以下是完整可运行示例(已适配 gorequest,并增强错误处理与资源安全):
package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"sync"
"github.com/parnurzeal/gorequest"
)
const workersCount = 16 // 可根据 CPU/网络带宽调整,通常 10–50 是合理范围
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, status: %s\n", url, len(body), resp.Status)
}
}
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, len(rawCSVdata))
var wg sync.WaitGroup
wg.Add(workersCount)
// 启动固定数量 worker
for i := 0; i < workersCount; i++ {
go getUrlWorker(urlChan, &wg)
}
// 生产者:推送所有 URL 到通道
for _, row := range rawCSVdata {
if len(row) < 2 {
log.Printf("Skipping invalid row (less than 2 columns): %v", row)
continue
}
urlChan <- row[1] // 假设 URL 在第二列(索引 1)
}
close(urlChan) // 关闭通道,通知 workers 无新任务
wg.Wait() // 等待所有 worker 完成
fmt.Println("All requests completed.")
}⚠️ 关键注意事项:
- 务必关闭通道:close(urlChan) 是终止 for url := range urlChan 循环的必要条件,否则 worker 将永久阻塞;
- 避免 goroutine 泄漏:原始代码中未 defer csvfile.Close() 且缺少错误检查,已修正;
- 错误不可忽略:真实场景中应检查 errs 切片并做重试/降级/日志记录;
- 性能权衡:通道缓冲区(如 make(chan string, 1000))可略微提升吞吐,但非必需;过度缓冲会增加内存占用;
- 现代替代建议:若项目允许,推荐升级至 net/http 原生客户端 + context.WithTimeout 实现更可控的超时与取消,gorequest 已多年未维护。
该方案代码简洁(<50 行)、逻辑清晰、无第三方依赖(除 gorequest 外),是 Go 中实现并发节流的「最小可行实践」。


















