跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何在Golang中实现一个并发图片下载器 Go语言协程池下载实战

应使用带缓冲的channel控制并发数,如sem := make(chan struct{}, 10),每次下载前<-sem、完成后sem<-struct{},避免goroutine泄漏导致OOM。 goroutine 泄漏导致内存爆满怎么办 不加限制地起
go downloadImage(url)
,几万张图下来进程直接 OOM。根本原因不是下载慢,是没等下载完协程就挂了,但 HTTP 连接、响应体缓冲、甚至未回收的
http.Client
实例还在占内存。 必须用带缓冲的 channel 控制并发数,比如
sem := make(chan struct{}, 10)
,每次下载前
sem ,结束后 <-sem
别复用全局
http.Client
时忽略
Timeout
和
Transport.MaxIdleConnsPerHost
,否则 DNS 缓存+连接池会悄悄吃光句柄 用
context.WithTimeout(ctx, 30*time.Second)
包裹每个请求,防止某张图卡死拖垮整个池 文件名冲突和路径安全怎么处理 直接用 URL 的
path.Base()
当文件名,遇到
https://i.example.com/1?w=800&h=600
就生成
1?w=800&h=600
,Linux 下创建失败,Windows 直接报错
invalid argument
。 用
url.Parse()
解析后取
u.Path
,再用
path.Base(u.Path)
;接着用
strings.TrimSuffix()
去掉查询参数干扰 对结果做一次
filepath.Clean()
防止
../
路径穿越,再用正则替换非法字符:
re := regexp.MustCompile(`[:"/\|?*]`)
→
re.ReplaceAllString(name, "_")
保存前先
os.MkdirAll(dir, 0755)
,别假设目录一定存在 下载失败后如何重试又不阻塞池子 单次失败就
return
,会导致任务丢失;全量重试又可能把超时请求反复塞进池子,压垮目标站或自己。 每张图最多重试 2 次,用
for i := 0; i 包裹核心逻辑,成功就 break
每次重试前加
time.Sleep(time.Second (即 1s、2s、4s),避免雪崩式重试
失败日志里必须打上原始
url
和
err.Error()
,否则排查时根本不知道哪张图卡在哪一步 别在重试逻辑里直接
go download(...)
—— 协程池已负责调度,重复起 goroutine 就破防了 为什么用
io.Copy
而不是
io.ReadAll
io.ReadAll(resp.Body)
会把整张图读进内存再写文件,10MB 的图 × 10 并发 = 至少 100MB 内存常驻;而大图下载场景下,内存占用比 CPU 更早成为瓶颈。 go语言参考手册 中文CHM版 Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译 下载 立即学习 “ go语言免费学习笔记(深入) ”; 直接
io.Copy(f, resp.Body)
,数据流式写入磁盘,内存峰值只取决于 OS page cache 大小 记得在
io.Copy
前设好
f.Sync()
或用
os.O_SYNC
标志(视可靠性要求而定),否则断电可能丢最后几 KB 如果要校验 MD5/SHA256,用
io.TeeReader(resp.Body, hashWriter)
,边读边算,不额外加载 事情说清了就结束。真正难的不是起十个协程,是让它们在出错、超时、路径异常、磁盘满、对方限速时,依然不泄漏、不卡死、不污染输出目录。

相关文章