Python中使用多进程处理类实例

114 阅读2分钟

在Python中,如果想使用多进程来处理类实例,会面临一个问题:类方法无法被腌制(pickle),因此无法通过管道发送给子进程。这导致无法将类实例中的方法发送到子进程中执行。

2. 解决方案

为了解决这个问题,可以采用以下方法:

  1. 将类方法转换为可腌制对象。这可以通过使用functools.partial函数来实现。functools.partial函数可以将类方法转换为一个可调用的对象,这个对象可以被腌制。

  2. 将类实例 pickle 化,并在子进程中反序列化。这可以通过使用pickle模块来实现。pickle模块可以将对象序列化为字节流,然后在子进程中反序列化为对象。

  3. 使用共享内存。这可以通过使用multiprocessing.Manager类来实现。multiprocessing.Manager类可以创建共享内存,然后子进程可以访问共享内存中的数据。

下面提供一个使用functools.partial函数来解决这个问题的示例代码:

import multiprocessing
import functools

class ProcessWorker(multiprocessing.Process):

    def __init__(self, task_q, result_q):
        multiprocessing.Process.__init__(self)
        self.task_q = task_q
        self.result_q = result_q

    def run(self):
        while True:
            next_task = self.task_q.get()
            if next_task is None:
                # Poison pill means shutdown
                print('%s: Exiting' % (self.name))
                self.task_q.task_done()
                break

            # 使用functools.partial将类方法转换为可腌制对象
            task = functools.partial(self.run_task, *next_task[1], **next_task[2])

            answer = task()
            self.task_q.task_done()
            self.result_q.put(answer)

    def run_task(self, *args, **kwargs):
        print('%s: %s' % (self.name, next_task))
        return next_task(*args, **kwargs)

class Worker(object):

    def __init__(self, config, index=None):
        self.config = config
        self.index = index

        # 启动 ProcessWorker
        if self.index is not None:
            self.task_q = multiprocessing.JoinableQueue()
            self.result_q = multiprocessing.Queue()

            self.process_worker = ProcessWorker(self.task_q, self.result_q)
            self.process_worker.start()

    def enqueue_process(target):  # No self, since it is a decorator

        # 使用functools.partial将类方法转换为可腌制对象
        task = functools.partial(target, self)

        def wrapper(*args, **kwargs):
            self.task_q.put([task, args, kwargs])

        return wrapper

    def fetch_results(self):

        # 等待子进程完成任务
        self.task_q.join()

        # 获取子进程的结果
        return self.result_q.get()

    @enqueue_process
    def run_long_command(self, command):
        print("I am running number % as process " % number, self.name)

        # 在子进程中执行命令
        p = Popen(command)
        p.wait()

    def close(self):
        self.task_q.put(None)
        self.task_q.join()


if __name__ == '__main__':
    config = ["some value", "something else"]
    index = 7
    workers = []
    for i in range(5):
        worker = Worker(config, index)
        worker.run_long_command("ls /")
        workers.append(worker)

    for worker in workers:
        worker.fetch_results()

    # Do more work... (this would actually be done in a distributor in another class)

    for worker in workers:
        worker.close()

在这个示例中,ProcessWorker类是一个多进程类,负责执行任务。Worker类是一个封装了ProcessWorker类的类,用于管理任务和结果。

在使用这个示例代码时,需要注意以下几点:

  1. 需要先创建Worker类的实例,然后才能使用enqueue_process装饰器来装饰类方法。
  2. 在子进程中执行任务时,需要使用functools.partial将类方法转换为可腌制对象。
  3. 在主进程中等待子进程完成任务时,需要使用task_q.join()方法。
  4. 在主进程中获取子进程的结果时,需要使用result_q.get()方法。