假设我的清单很大,并且正在执行如下操作:
for item in items: try: api.my_operation(item) except: print 'error with item'
我的问题有两个:
我想使用多线程一次启动一堆api.my_operations,以便我可以一次处理5或10甚至100个项目。
如果my_operation()返回一个异常(因为也许我已经处理过该项目),那就可以了。它不会破坏任何东西。循环可以继续到下一个项目。
注意 :这适用于Python 2.7.3
首先,在Python中,如果您的代码是CPU绑定的,那么多线程将无济于事,因为只有一个线程可以持有全局解释器锁,因此一次只能运行Python代码。因此,您需要使用进程,而不是线程。
如果您的操作“永远需要返回”是因为它是IO绑定的,也就是说,正在等待网络或磁盘副本等,这是不正确的。我稍后再讲。
接下来,一次处理5个或10个或100个项目的方法是创建5个或10个或100个工人的池,并将这些项目放入由工人服务的队列中。幸运的是,stdlibmultiprocessing和concurrent.futures库都为您包装了大多数细节。
multiprocessing
concurrent.futures
前者在传统编程方面更强大,更灵活。如果您需要编写将来的等待,则后者更简单;对于微不足道的情况,选择哪一个并不重要。(在这种情况下,最明显的实现分别是带有3行,带有带有futures4行multiprocessing。)
futures
如果您使用的是2.6-2.7或3.0-3.1,futures则不是内置的,但您可以从PyPI(pip install futures)安装它。
pip install futures
最后,如果您可以将整个循环迭代转换为函数调用(通常可以(例如,传递给map)),那么并行化处理通常会简单得多,所以让我们首先进行以下操作:
map
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)
如果您有很多相对较小的工作,则多处理的开销可能会浪费收益。解决该问题的方法是将工作分批处理成更大的工作。例如(使用grouper从itertools食谱,您可以复制并粘贴到你的代码,或者从一开始more- itertoolsPyPI上的项目):
grouper
itertools
more- itertools
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。
ProcessPoolExecutor
ThreadPoolExecutor
如果不确定代码是受CPU约束还是受IO约束,只需尝试两种方法即可。
我可以在python脚本中为多个功能执行此操作吗?例如,如果我想并行化的代码中其他地方有另一个for循环。是否可以在同一脚本中执行两个多线程函数?
是。实际上,有两种不同的方法可以做到这一点。
首先,您可以共享同一个(线程或进程)执行程序,并可以在多个地方使用它而没有问题。任务和未来的重点在于它们是独立的。您不在乎它们在哪里运行,只需将它们排队并最终得到答案即可。
另外,您可以在同一程序中有两个执行程序,这没有问题。这会降低性能,如果您同时使用两个执行器,最终将试图在8个内核上运行(例如)16个繁忙线程,这意味着将需要进行一些上下文切换。但是有时候值得这样做,因为例如两个执行者很少同时忙,这会使您的代码变得更简单。也许一个执行程序正在运行可能需要一段时间才能完成的非常大的任务,而另一个执行程序却正在运行需要尽快完成的非常小的任务,因为响应能力比部分程序的吞吐量更重要。
如果您不知道哪个适合您的程序,通常是第一个。