您好,我遇到一种情况,我正在调用一些API来获取电影列表。对于列表中的每个记录,我调用另一个API。我想使for
循环并行以获得更好的性能。以下是我的示例代码。
movies = []
for movie in collection:
tmdb_movie = tmdb.get_movie(movie['detail']['ids']['tmdb_id'])
movies.append(tmdb_movie)
return tmdb_movie
所以我使用多重处理的解决方案如下:
pool = Pool()
output = pool.map(tmdb.get_movie, [movie['detail']['ids']['tmdb_id'] for movie in collection])
但是当我执行此代码时,出现以下错误
PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup __builtin__.instancemethod failed
如果有人可以帮助我在python 2.7中使此功能并行化,我将不胜感激。
答案 0 :(得分:2)
最好的选择是使用线程。 Python中的线程不能并行使用CPU,但是在发生阻塞操作时它们可以并发执行。进程虽然可以真正并行运行,但是启动和通信速度很慢,并且更适合于CPU负担较大的工作负载。另外,正如您在问题中指出的那样,有时可能难以启动流程。
您可以使用有些机密(即未公开但实际上众所周知的)的multiprocessing.pool.ThreadPool
类。如果要执行多次,则可以在开始时创建一个池,然后重新使用它。您只需要确保在程序退出时调用pool.close()
甚至可能调用pool.join()
。
from multiprocessing.pool import ThreadPool
# Global/class variables
NUM_THREADS = 5
pool = ThreadPool(NUM_THREADS)
# Inside some function/method
return pool.map(lambda movie: tmdb.get_movie(movie['detail']['ids']['tmdb_id']), movies)
# On exit
pool.close() # Prevents more jobs to be submitted
pool.join() # Waits until all jobs are finished
答案 1 :(得分:2)
您的问题非常广泛,并省略了许多细节,因此,这里概述了需要做的事情。为避免使用PicklingError
,将在每个进程中打开数据库-可以使用 initializer
函数(在下面的示例代码中命名为start_process()
)来完成。
注意:由于初始化数据库以执行一个队列所涉及的开销,因此@jdehesa的多线程方法在这种情况下可能是更好的策略(线程通常使共享全局变量的开销降低)。另外,您可以使get_movie()
接口函数每次被调用时都可以处理多个id
(即它们的“批次”)。
class Database:
""" Mock implementation. """
def __init__(self, *args, **kwargs):
pass # Open/connect to database.
def get_movie(self, id):
return 'id_%s_foobar' % id
import multiprocessing as mp
def start_process(*args):
global tmdb
tmdb = Database(*args)
def get_movie(id):
tmdb_movie = tmdb.get_movie(id)
return tmdb_movie
if __name__ == '__main__':
collection = [{'detail': {'ids': {'tmdb_id': 1}}},
{'detail': {'ids': {'tmdb_id': 2}}},
{'detail': {'ids': {'tmdb_id': 3}}}]
pool_size = mp.cpu_count()
with mp.Pool(processes=pool_size, initializer=start_process,
initargs=('init info',)) as pool:
movies = pool.map(get_movie, (movie['detail']['ids']['tmdb_id']
for movie in collection))
print(movies) # -> ['id_1_foobar', 'id_2_foobar', 'id_3_foobar']
一种多处理替代方案将允许多个进程在某种程度上共享数据库而无需每次都与其连接,这将是定义一个自定义multiprocessing.Manager()
,它一次打开数据库并提供一个接口以获得指定ID的一部或多部电影的信息。联机文档的Sharing state between processes部分(在 Server Process 小节中)也对此进行了讨论。内置的Manager
支持多种容器类型,list
,dict
和Queue
s。
下面是示例代码,显示了如何为数据库创建自己的自定义管理器。如果取消注释对print()
的调用,则会看到仅创建了一个Database
实例:
class Database:
""" Mock implementation. """
def __init__(self, *args, **kwargs):
# print('Database.__init__')
pass # Open/connect to database.
def get_movie(self, id):
return 'id_%s_foobar' % id
from functools import partial
import multiprocessing as mp
from multiprocessing.managers import BaseManager
class DB_Proxy(object):
""" Shared Database instance proxy class. """
def __init__(self, *args, **kwargs):
self.database = Database(*args, **kwargs)
def get_movie(self, id):
# print('DB_Proxy.get_movie')
tmdb_movie = self.database.get_movie(id)
return tmdb_movie
class MyManager(BaseManager): pass # Custom Manager
MyManager.register('DB_Proxy', DB_Proxy)
if __name__ == '__main__':
collection = [{'detail': {'ids': {'tmdb_id': 1}}},
{'detail': {'ids': {'tmdb_id': 2}}},
{'detail': {'ids': {'tmdb_id': 3}}}]
manager = MyManager()
manager.start()
db_proxy = manager.DB_Proxy('init info')
pool_size = mp.cpu_count()
with mp.Pool(pool_size) as pool:
movies = pool.map(db_proxy.get_movie,
(movie['detail']['ids']['tmdb_id']
for movie in collection))
print(movies) # -> ['id_1_foobar', 'id_2_foobar', 'id_3_foobar']