并行化这个网络滚动循环的最佳方法是什么?

时间:2016-11-20 17:54:41

标签: python multiprocessing

我正在制作一个网络浏览器,我有一些"睡眠"使爬网时间很长的函数。 现在我正在做:

for speciality in range(1,25):
    for year in range(1997, 2017):
        for quarter in [1,2]:
            deal_with (driver, year, quarter, speciality, ok)

deal_with功能正在打开几个网页,等待几秒钟完成html下载,然后再继续。执行时间非常长:有25 * 10 * 2 = 500个循环,循环不少于一分钟。

我想使用我的4个物理内核(8个线程)来享受并行性。 我读过关于龙卷风,多处理,joblib ......并且我不能真正考虑一个简单的解决方案来适应我的代码。

欢迎任何见解: - )

2 个答案:

答案 0 :(得分:0)

如果您正在使用python3,我会查看asycio module。我相信你可以用deal_with装饰@asyncio.coroutine。您可能还需要调整deal_with所做的事情以正确使用事件循环。

答案 1 :(得分:0)

tl; dr 在没有完全理解您所面临的瓶颈的情况下投资任何选择都无济于事。

在一天结束时,只有两种基本方法可以扩展这样的任务:

多处理

您启动了许多Python流程,并将任务分配给每个流程。这是您认为现在可以帮助您的方法。

有关其工作原理的一些示例代码,但您可以使用任何适当的包装器:

import multiprocessing

# general rule of thumb: launch twice as many processes as cores

process_pool = multiprocessing.Pool(8) # launches 8 processes

# generate a list of all inputs you wish to feed to this pool

inputs = []

for speciality in range(1,25):
    for year in range(1997, 2017):
        for quarter in [1,2]:
            inputs.append((driver, year, quarter, speciality, ok))

# feed your list of inputs to your process_pool and print it when done
print(process_pool.map(deal_with, inputs))

如果这就是你想要的,你现在就可以停止阅读了。

异步执行

在这里,您满足于单个线程或进程,但您不希望它等待网络读取或磁盘搜索等内容等待 - 您希望它继续执行其他操作,更多等待的重要事情。

Python 3中提供了真正的本机异步I / O支持,并且在Twisted网络库之外的Python 2.7中不存在。

import concurrent.futures

# generate a list of all inputs you wish to feed to this pool

inputs = []

for speciality in range(1,25):
    for year in range(1997, 2017):
        for quarter in [1,2]:
            inputs.append((driver, year, quarter, speciality, ok))

# produce a pool of processes, and make sure they don't block each other
# - get back an object representing something yet to be resolved, that will
# only be updated when data comes in.

with concurrent.futures.ProcessPoolExecutor() as executor:
    outputs = [executor.submit(input_tuple) for input_tuple in inputs]

    # wait for all of them to finish - not ideal, since it defeats the purpose
    # in production, but sufficient for an example

    for future_object in concurrent.futures.as_completed(outputs):
         # do something with future_object.result()

那有什么区别?

我的主要观点是强调从技术清单中选择并不像确定真正的瓶颈在哪里那么难。

在上面的示例中,没有任何区别。两者都遵循一个简单的模式:

  1. 有很多工人
  2. 允许这些工作人员立即从任务队列中挑选一些东西
  3. 当一个人有空时,立即让他们在下一个工作。
  4. 因此,如果您逐字地遵循这些示例,即使它们使用完全不同的技术并声称使用完全不同的技术,您也不会完全获得概念上的差异。

    如果你用这种模式编写它,你选择的任何技术都是徒劳的 - 即使你会获得一些加速,如果你期望大幅提升性能,你会非常失望。

    为什么这种模式不好?因为它无法解决您的问题。

    您的问题很简单:您已等待。当你的过程正在等待某些东西回来时,它无法做任何其他事情!它无法为您调用更多页面。它无法处理传入的任务。它所能做的只是等待。

    拥有更多最终等待的流程并不是真正的解决方案。如果你把它分成团,那么必须向滑铁卢进军的军队才会更快 - 每个团最终都要睡觉,尽管他们可能会在不同的时间和不同的长度睡觉,会发生什么,他们都会几乎在同一时间到达。

    你需要什么是一支从不睡觉的军队。

    那你该怎么办?

    抽象所有 I / O将任务绑定到非阻塞的内容中。这是你真正的瓶颈。如果你正在等待网络响应,不要让穷人过程只是坐在那里 - 给它做点什么。

    您的任务有点困难,因为默认情况下从套接字读取是阻塞的。这是操作系统的方式。值得庆幸的是,需要让Python 3来解决它(尽管这始终是首选解决方案) - {2.7}已存在于{@ 3}}库(asyncore)在后台真正进行网络读写。

    只有一种情况需要在Python中使用真正的多处理,如果您正在进行CPU限制或CPU密集型工作。根据你的描述,听起来并非如此。

    简而言之,您应该编辑deal_with函数以避免初期等待。如果需要,使用Twisted或asyncore中的合适抽象,在后台等待。但是不要让它完全消耗你的过程。