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))
如果这就是你想要的,你现在可以停止阅读。
异步执行
在这里,您对单个线程或进程感到满意,但您不希望它闲置等待诸如网络读取或磁盘搜索之类的东西回来 - 您希望它继续执行其他更多操作等待中的重要事项。
真正的原生异步 I/O 支持在 Python 3 中提供,但在 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()
那么有什么区别呢?
我的主要观点是强调从技术列表中进行选择并不像找出真正的瓶颈在哪里那么难。
在上面的示例中,没有任何区别。两者都遵循一个简单的模式:
- 有很多工人
- 允许这些工作人员立即从任务队列中挑选一些东西
- 当一个人有空时,让他们立即处理下一个人。
因此,如果您逐字逐句地按照这些示例进行操作,您将不会完全获得概念上的差异,即使它们使用完全不同的技术并声称使用完全不同的技术。
如果您以这种模式编写,您选择的任何技术都将是徒劳的 - 即使您会获得一些加速,但如果您期望大幅提升性能,您将会非常失望。
为什么这种模式不好?因为它不能解决你的问题。
你的问题很简单:你有等待。当你的进程在等待某些东西回来时,它不能做任何其他事情!它不能为您调用更多页面。它无法处理传入的任务。它所能做的就是等待。
拥有更多最终等待的进程并不是真正的解决方案。如果你将一支必须进军滑铁卢的军队分成几个团,它不会更快——每个团最终都必须睡觉,尽管他们可能会在不同的时间和不同的时间睡觉,而将会发生的事情是他们所有人都会几乎在同一时间到达。
您需要的是一支不眠不休的军队。
那么你应该怎么做?
将所有 I/O 绑定任务抽象为非阻塞。这是你真正的瓶颈。如果您正在等待网络响应,请不要让糟糕的进程坐在那里 - 让它有事可做。
您的任务有些困难,因为默认情况下从套接字读取是阻塞的。这就是操作系统的方式。幸运的是,您不需要 需要 Python 3 来解决它(尽管这始终是首选解决方案) - Python 2.7 中已经存在 asyncore 库 (though Twisted is comparably superior in every way) 来进行网络读取并在后台真正写入。
只有一种情况需要在 Python 中使用真正的多处理,那就是您正在执行 CPU 密集型或 CPU 密集型工作。从你的描述来看,好像不是这样的。
简而言之,您应该编辑您的deal_with 函数以避免初期等待。如果需要,使用来自 Twisted 或 asyncore 的适当抽象,在后台等待。但是不要让它完全消耗你的过程。