首先,在Python中,如果您的代码受CPU约束,那么多线程将无济于事,因为只有一个线程可以持有全局解释器锁,因此一次只能运行Python代码。 因此,您需要使用进程,而不是线程。
如果您的操作“永远需要返回”是因为它是IO绑定的,也就是说,正在等待网络或磁盘副本等,这是不正确的。 我稍后再讲。
接下来,一次处理5个或10个或100个项目的方法是创建5个或10个或100个工人的池,并将这些项目放入由工人服务的队列中。 幸运的是,stdlib multiprocessing和ProcessPoolExecutor库都为您提供了大部分详细信息。
前者在传统编程方面更强大,更灵活。 如果您需要编写将来的等待,则后者更简单; 对于微不足道的情况,选择哪一个并不重要。 (在这种情况下,最明显的实现分别是3行与ProcessPoolExecutor,4行与ThreadPoolExecutor。)
如果您使用的是2.6-2.7或3.0-3.1,则没有内置ProcessPoolExecutor,但您可以从PyPI(ThreadPoolExecutor)安装它。
最后,如果您可以将整个循环迭代转换为函数调用(通常可以将其传递给ProcessPoolExecutor),那么并行化处理通常会容易得多,所以让我们首先进行以下操作:
def try_my_operation(item):
try:
api.my_operation(item)
except:
print('error with item')
放在一起:
executor = concurrent.futures.ProcessPoolExecutor(10)
futures = [executor.submit(try_my_operation, item) for item in items]
concurrent.futures.wait(futures)
如果您有很多相对较小的工作,则多处理的开销可能会浪费收益。 解决该问题的方法是将工作分批处理成更大的工作。 例如(使用ProcessPoolExecutor872食谱中的ProcessPoolExecutor,您可以将其复制并粘贴到您的代码中,或者从PyPI上的ThreadPoolExecutor项目获得):
def try_multiple_operations(items):
for item in items:
try:
api.my_operation(item)
except:
print('error with item')
executor = concurrent.futures.ProcessPoolExecutor(10)
futures = [executor.submit(try_multiple_operations, group)
for group in grouper(5, items)]
concurrent.futures.wait(futures)
最后,如果您的代码受IO约束怎么办? 这样线程就和进程一样好,并且开销更少(限制更少,但是在这种情况下这些限制通常不会影响您)。 有时,“较少的开销”足以表示您不需要使用线程进行批处理,但是您需要使用进程,这是一个不错的选择。
那么,如何使用线程而不是进程? 只需将ProcessPoolExecutor更改为ThreadPoolExecutor。
如果不确定代码是受CPU约束还是受IO约束,只需尝试两种方法即可。
我可以在python脚本中为多个功能执行此操作吗? 例如,如果我要并行化的代码中其他地方有另一个for循环。 是否可以在同一脚本中执行两个多线程函数?
是。 实际上,有两种不同的方法可以做到这一点。
首先,您可以共享同一(线程或进程)执行程序,并可以在多个地方使用它而没有问题。 任务和未来的重点在于它们是独立的。 您不在乎它们在哪里运行,只需将它们排队并最终得到答案即可。
或者,您可以在同一个程序中有两个执行程序,没有问题。 这会降低性能,如果您同时使用两个执行器,最终将试图在8个内核上运行(例如)16个繁忙线程,这意味着将需要进行一些上下文切换。 但是有时候这样做是值得的,因为,例如,两个执行器很少同时忙,这会使您的代码更加简单。 也许一个执行程序正在运行可能需要一段时间才能完成的非常大的任务,而另一个执行程序却正在运行需要尽快完成的非常小的任务,因为响应能力比部分程序的吞吐量更重要。
如果您不知道哪个适合您的程序,通常是第一个。