You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何建立共享会话并优化多线程附件下载性能?

问题分析与解决

为什么多线程反而更慢?

  • 你当前的代码如果没有用requests.Session复用连接,每个请求都会新建TCP连接、重复握手认证,多线程下会产生大量连接开销,拖慢整体速度。
  • 无限制创建45+线程会导致系统上下文切换频繁,CPU额外开销剧增,反而比串行效率更低。
  • 原代码没有等待所有线程执行完成,可能出现程序提前退出、文件下载不完整的情况。

解决方案步骤

1. 用共享requests.Session减少连接开销

requests.Session会维护连接池,自动复用TCP连接,避免每次请求都重新握手和认证,大幅降低服务器连接与认证的耗时,且线程环境下只要不修改会话配置(比如headers)就是安全的。

2. 用线程池控制并发数

不要一次性创建大量线程,用ThreadPoolExecutor设置合理的最大并发数(比如10-20,可根据服务器并发限制调整),避免上下文切换过载。

3. 优化文件写入逻辑

针对大文件使用分块写入,既节省内存也更稳定,避免一次性加载整个文件到内存。

4. 等待所有任务完成

确保所有下载线程执行完毕再退出,避免文件未完成下载。

修改后的完整代码

import os
import json
import requests
from concurrent.futures import ThreadPoolExecutor

# 全局共享会话,初始化时配置好认证和请求头
session = requests.Session()
session.headers.update({
    'Content-Type': 'application/json',
    'Authorization': f"Bearer {token}"
})

def download_File(name, url):
    # 使用共享会话发起请求,开启流式下载
    with session.get(url, stream=True) as res:
        res.raise_for_status()  # 捕获HTTP错误,避免静默失败
        with open(name, "wb") as f:
            # 分块写入文件,每块1MB
            for chunk in res.iter_content(chunk_size=1024*1024):
                f.write(chunk)

def downloadFiles(outputPath, page_id):
    # 确保输出目录存在
    os.makedirs(outputPath, exist_ok=True)
    
    # 用共享会话获取附件列表
    attachments_res = session.get(f"https://server.com/server/rest/api/content/{page_id}/child/attachment")
    attachments_res.raise_for_status()
    attachments = attachments_res.json()  # 直接用response内置的json解析,简化代码
    
    linksDict = {}
    for picture in attachments["results"]:
        # 修正下载链接拼接(注意原链接的域名是否正确)
        download_url = f"https://server.com/server{picture['_links']['download']}"
        linksDict[picture['title']] = download_url
    
    # 初始化线程池,设置最大并发数
    max_workers = 15
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 提交所有下载任务
        futures = []
        for title, url in linksDict.items():
            file_path = os.path.join(outputPath, title)
            futures.append(executor.submit(download_File, file_path, url))
        
        # 等待所有任务完成,捕获异常处理下载失败的情况
        for future in futures:
            try:
                future.result()
            except Exception as e:
                print(f"下载失败: {str(e)}")

关键改动说明

  • 共享会话:全局初始化一个Session,所有请求复用连接池,彻底减少连接与认证的重复开销。
  • 线程池控并发:通过ThreadPoolExecutor限制同时运行的线程数,平衡并发效率与系统资源占用。
  • 流式下载:stream=True配合分块写入,适配大文件下载,降低内存占用。
  • 错误处理:加入raise_for_status()捕获HTTP错误,通过future.result()捕获下载异常,避免静默失败。
  • 目录预创建:提前创建输出目录,避免文件写入时的路径错误。

额外建议

  • 测试不同的max_workers值,找到最适合你服务器环境的并发数(比如从10开始逐步上调,观察下载速度变化)。
  • 如果服务器有速率限制,可以加入重试逻辑(比如用tenacity库实现自动重试)。

内容的提问来源于stack exchange,提问作者ahmad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:55:26