【发布时间】:2016-10-27 00:07:57
【问题描述】:
我正在尝试使用 requests_futures 从 Amazon S3 下载大约 3,000 个文件(每个文件大小可能为 3 MB),但下载速度在大约 900 之后严重减慢,并且实际上开始运行比基本 for-循环。
我似乎没有耗尽内存或 CPU 带宽。然而,我的机器上的 Wifi 连接似乎几乎没有减慢:我从每秒几千个数据包下降到只有 3-4 个。最奇怪的是,在 Python 进程退出之前我无法加载任何网站并且我重新启动了我的 wifi 适配器。
究竟是什么原因造成的,我该如何调试它?
如果有帮助,这是我的 Python 代码:
import requests
from requests_futures.sessions import FuturesSession
from concurrent.futures import ThreadPoolExecutor, as_completed
# get a nice progress bar
from tqdm import tqdm
def download_threaded(urls, thread_pool, session):
futures_session = FuturesSession(executor=thread_pool, session=session)
futures_mapping = {}
for i, url in enumerate(urls):
future = futures_session.get(url)
futures_mapping[future] = i
results = [None] * len(futures_mapping)
with tqdm(total=len(futures_mapping), desc="Downloading") as progress:
for future in as_completed(futures_mapping):
try:
response = future.result()
result = response.text
except Exception as e:
result = e
i = futures_mapping[future]
results[i] = result
progress.update()
return results
s3_paths = [] # some big list of file paths on Amazon S3
def make_s3_url(path):
return "https://{}.s3.amazonaws.com/{}".format(BUCKET_NAME, path)
urls = map(make_s3_url, s3_paths)
with ThreadPoolExecutor() as thread_pool:
with requests.session() as session:
results = download_threaded(urls, thread_pool, session)
使用我尝试过的各种方法进行编辑:
-
time.sleep(0.25)在每个future.result()之后(性能在 900 左右急剧下降) - 4 个线程,而不是默认的 20 个(性能会逐渐下降,但仍会下降到基本为零)
- 1 个线程(性能在 900 左右急剧下降,但间歇性恢复)
- ProcessPoolExecutor 而不是 ThreadPoolExecutor(性能在 900 左右急剧下降)
- 当状态大于 200 时调用
raise_for_status()抛出异常,然后通过将其打印为警告来捕获此异常(不显示警告) - 在完全不同的网络上使用以太网而不是 wifi(没有变化)
- 在普通请求会话中创建期货,而不是使用 FutureSession(这是我最初所做的,在尝试解决问题时发现 requests_futures)
-
只运行下载只运行故障点附近的一小部分文件(例如文件 850 到文件 950)——这里的性能很好,
print(response.status_code)一直显示 200,并且没有发现异常。
不管怎样,我之前使用类似的方法能够在大约 4 秒内从 S3 下载约 1500 个文件,尽管文件要小一个数量级
今天有时间我会尝试的事情:
- 使用 for 循环
- 在 shell 中使用 Curl
- 在 shell 中使用 Curl + Parallel
- 使用 urllib2
编辑:看起来线程的数量是稳定的,但是当性能开始变差时,“空闲唤醒”的数量似乎从几百飙升到几千。这个数字是什么意思,我可以用它来解决这个问题吗?
未来的编辑 2:我从来没有最终解决这个问题。我没有在一个应用程序中完成所有操作,而是将文件列表分块并在单独的终端窗口中使用单独的 Python 调用运行每个块。丑陋但有效!问题的原因将永远是个谜,但我认为这是我当时工作机器的网络堆栈深处的某种问题。
【问题讨论】:
-
当您向 Wi-Fi 驱动程序发送会话打开请求时,这可能是您的 Wi-Fi 驱动程序中的一个错误,即使不是,创建 1000 个线程似乎也不是一个好策略。为什么不尝试
with ThreadPoolExecutor(max_workers=n) as thread_pool:并搜索不会导致问题的n?注意“3.5版更改:如果max_workers为None或未给出,则默认为机器上的处理器数量,乘以5”,according to the docs -
我更新了我的评论;但是,您的
FuturesSession()调用可能会使正在使用的线程数加倍 - 试试n_cores * 2.5? -
@KenY-N 我确实在使用 3.5,所以这将有 20 个工人。我会尝试更少。
-
@KenY-N 我首先从那个答案中得到了使用 FutureSession 的想法
-
从哪里下载文件?它来自一个服务器,还是一小组服务器?是否会因为服务器看到来自您机器的大量请求而限制下载速率?
标签: multithreading python-3.x python-requests python-multithreading concurrent.futures