多线程下载大文件时需为每个线程独享requests.session()实例,分片请求须显式设置range头并校验content-range与实际长度,文件写入应使用os.open+os.lseek+os.write确保偏移准确,max_workers建议设为4–8并配合连接池控制与重试延迟。

多线程下载大文件时,requests.Session() 必须每个线程独享
共享一个 requests.Session() 实例在多线程下会引发连接复用冲突、Cookie 污染、甚至 ConnectionPoolFull 错误。Python 的 requests 默认 Session 不是线程安全的。
实操建议:
- 在线程函数内部创建独立的
Session,或通过threading.local()绑定每线程专属 Session - 显式设置
session.headers.update({'Range': f'bytes={start}-{end}'})实现分片请求(需服务端支持 HTTP Range) - 避免使用全局
requests.get(),它底层隐式使用共享 Session
分片下载后写入文件必须用 seek() + os.O_RDWR | os.O_CREAT 打开
多个线程并发写入同一文件时,若用普通 'wb' 模式打开,会相互覆盖;若用 'ab' 则无法跳转到指定偏移写入,导致组装错位。
实操建议:
- 先用
open(path, 'wb')创建空文件并truncate()到总大小(需提前 HEAD 获取Content-Length) - 每个线程用
os.open(path, os.O_RDWR)获取底层 fd,再用os.lseek(fd, start, os.SEEK_SET)定位,最后os.write(fd, data) - 不要用
file.write(),它不保证原子偏移写入,且受 Python 文件缓冲干扰
concurrent.futures.ThreadPoolExecutor 的 max_workers 不宜超过 10
HTTP 下载是 I/O 密集型任务,但过多线程反而触发 DNS 阻塞、TCP 连接竞争、服务端限流(如 429),实测多数 CDN 或对象存储在 5–8 线程时吞吐达峰。
实操建议:
- 从
max_workers=4起步,用time.perf_counter()对比不同值下的总耗时 - 配合
session.mount('https://', requests.adapters.HTTPAdapter(pool_maxsize=10))控制单 session 连接池大小,防止“too many open files” - 加
time.sleep(0.1)在重试逻辑中(如 5xx 错误后),避免雪崩式重试
组装完成前必须校验每个分片的 Content-Range 和实际字节数
网络中断、代理截断、服务端 bug 可能导致某分片返回少于预期的数据(例如请求 1MB 却只返回 12KB),而线程仍会写入——最终文件损坏但无报错。
实操建议:
- 下载后立即检查
response.headers.get('Content-Range')是否匹配请求范围,且len(response.content) == expected_size - 对每个分片计算
hashlib.sha256(data).hexdigest()并与服务端提供的分片摘要比对(如有) - 组装完成后统一执行
hashlib.md5(open(path,'rb').read()).hexdigest()校验整体一致性,而非仅依赖分片成功信号
Range 请求返回 200 而非 206,却不带 Content-Range 头,这种边界情况必须主动识别并拒收。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











