使用Python的concurrent.futures并行处理对象

时间:2014-09-03 19:37:08

标签: python multithreading python-3.x future concurrent.futures

我刚刚开始使用Python 3中的库concurrent.futures来应用图像列表中的许多函数,以便处理这些图像并重新整形。 功能是resize(height, width)opacity(number)

另一方面,我有images()函数可以生成类似文件的对象, 所以我尝试使用此代码并行处理我的图像:

import concurrent.futures
From mainfile import images
From mainfile import shape


def parallel_image_processing :
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    future = executor.submit(images)
    for fileobject in future.result() :
        future1 = executor.submit( shape.resize, fileobject, "65","85")
        future2 = executor.submit( shape.opacity, fileobject, "0.5")

有人可以告诉我是否在正确的道路上实现这一目标吗?

1 个答案:

答案 0 :(得分:1)

我建议让images只返回路径,而不是打开文件对象:

def images():
    ...
    yield os.path.join(image_dir[0], filename)

然后用这个:

from functools import partial

def open_and_call(func, filename, args=(), kwargs={}):
    with open(filename, 'rb') as f:
        return func(f, *args, **kwargs)

def parallel_image_processing():
    resize_func = partial(open_and_call, shape.resize, args=("65", "85"))
    opacity_func = partial(open_and_call, shape.opacity, args=("0.5"))
    img_list = list(images())
    with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
        futures1 = executor.map(resize_func, img_list)
        futures2 = executor.map(opacity_func, img_list)

        concurrent.futures.wait([futures1, futures2])


if __name__ == "__main__":
    # Make sure the entry point to the function that creates the executor 
    # is inside an `if __name__ == "__main__"` guard if you're on Windows.
    parallel_image_processing()

如果您正在使用CPython(而不是像Jython那样没有GIL的替代实现),您不想使用ThreadPoolExecutor,因为图像处理是CPU密集型的;由于GIL,在CPython中一次只能运行一个线程,所以如果你为用例使用线程,你实际上并没有做任何事情。相反,使用ProcessPoolExecutor,它将使用进程而不是线程,完全避免使用GIL。请注意,这就是为什么我建议不从images返回类似文件的对象 - 您无法将打开的文件句柄传递给工作进程。您必须在工作人员中打开文件。

为此,我们让executor调用一个小填充函数(open_and_call),它将在工作进程中打开文件,然后调用resize / {{ 1}}具有正确参数的函数。

我还使用opacity代替executor.map,以便我们可以为executor.submit返回的每个项目调用resize / opacity一个明确的循环。我使用images()来使用functools.partial调用带有多个参数的函数变得更容易(只允许你调用带有单个参数的函数)。

还没有必要在执行者中调用executor.map,因为您无论如何都要等待其结果。只需将其称为普通功能即可。在调用images()之前,我将images()返回的生成器对象转换为list。如果您担心内存使用情况,可以直接在每次map来电中致电images(),但如果没有,只需拨打map一次就可以更快将其存储为列表。