多线程HTTP GET请求在下载约900次后严重减慢

时间:2016-10-27 00:07:57

标签: multithreading python-3.x python-requests python-multithreading concurrent.futures

我正在尝试使用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-loop
  • 在shell中使用Curl
  • 在shell中使用Curl + Parallel
  • 使用urllib2

编辑:看起来线程的数量是稳定的,但是当性能开始变坏时,“空闲唤醒上升”的数量似乎从几百到几千。这个数字意味着什么,我可以用它来解决这个问题吗?

1 个答案:

答案 0 :(得分:0)

这不是一个惊喜。

当线程数多于核心数时,您不会获得任何并行性。

您可以通过将问题简化为具有多个线程的单个核心来证明这一点。

会发生什么?您一次只能运行一个线程,因此操作系统上下文会切换每个线程以便为每个人提供一个转弯。一个线程起作用,其他线程一直在睡觉,直到它们被唤醒为止。在这种情况下,你不能比单线程更好。

你可能会做得更糟,因为每个线程(每个1MB)分配的上下文切换和内存也有价格。

阅读Amdahl's Law