
本文介绍一种轻量、易理解的生产者-消费者模式,使用 channel 与 sync.waitgroup 在 go 中精确控制最大 16 个 goroutine 并发执行 http get 请求,避免资源耗尽或服务端限流。
本文介绍一种轻量、易理解的生产者-消费者模式,使用 channel 与 sync.waitgroup 在 go 中精确控制最大 16 个 goroutine 并发执行 http get 请求,避免资源耗尽或服务端限流。
在处理大量 URL 批量请求(如 10k 行 CSV)时,无节制地启动 goroutine(例如 go getUrl(...))极易导致内存暴涨、文件描述符耗尽,甚至触发目标服务器的限流或封禁。最简洁且健壮的解决方案是采用固定数量工作协程 + 任务通道的经典 producer-consumer 模式。
核心思路如下:
- 启动固定数量(如 16 个)的长期运行 worker goroutine;
- 所有 URL 通过无缓冲或带缓冲 channel 统一派发;
- 使用 sync.WaitGroup 确保主协程等待全部 worker 完成;
- 任务发送完毕后关闭 channel,使 worker 自然退出。
以下是完整可运行示例(已适配原代码逻辑,并增强错误处理与资源安全):
package main
import (
"encoding/csv"
"fmt"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/parnurzeal/gorequest"
)
const MaxConcurrency = 16 // 可轻松调整为任意值
func getUrlWorker(urlChan 0 {
log.Printf("ERROR fetching %s: %v", url, errs[0])
continue
}
if resp.StatusCode != http.StatusOK {
log.Printf("WARN %s returned status %d", url, resp.StatusCode)
}
// 可选:处理 body,例如解析 JSON 或保存内容
_ = 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
records, err := reader.ReadAll()
if err != nil {
log.Fatal("failed to read CSV:", err)
}
// 过滤空行或无效 URL(健壮性建议)
var urls []string
for _, r := range records {
if len(r) > 1 && r[1] != "" {
urls = append(urls, r[1])
}
}
fmt.Printf("Loaded %d valid URLs\n", len(urls))
// 创建带缓冲 channel(提升吞吐,非必需但推荐)
urlChan := make(chan string, 128)
var wg sync.WaitGroup
wg.Add(MaxConcurrency)
// 启动固定数量 worker
for i := 0; i <p>✅ <strong>关键优势说明</strong>: </p>
- 零第三方依赖:仅需标准库 sync 和 channel,gorequest 可替换为 net/http;
- 内存可控:worker 数量恒定,channel 缓冲区可调,杜绝 goroutine 泛滥;
- 优雅终止:close(urlChan) + range 保证 worker 自然退出,无需额外信号机制;
- 易于扩展:后续可轻松加入重试、结果收集、速率限制(如 time.Ticker)、或熔断逻辑。
⚠️ 注意事项:
- 原代码中 defer csvfile.Close() 在 err 检查前执行,存在 panic 风险——已修正为先检查 err 再 defer;
- gorequest 已多年未维护,生产环境推荐迁移到 net/http + context.WithTimeout,或使用更现代的客户端如 resty;
- 若需收集响应结果(如状态码、耗时、错误),可将 getUrlWorker 改为向结果 channel 发送结构体,并由主 goroutine 汇总。
该方案以最小认知成本实现高可靠并发控制,是 Go 初学者与工程实践者都应掌握的基础范式。











