简体   繁体   English

如何在Python中使用类实例的多处理?

[英]How to use multiprocessing with class instances in Python?

I am trying to create a class than can run a separate process to go do some work that takes a long time, launch a bunch of these from a main module and then wait for them all to finish. 我试图创建一个类,而不是可以运行一个单独的进程去做一些需要很长时间的工作,从主模块中启动一堆这些,然后等待它们全部完成。 I want to launch the processes once and then keep feeding them things to do rather than creating and destroying processes. 我想一次启动流程,然后继续为他们提供待办事项,而不是创建和销毁流程。 For example, maybe I have 10 servers running the dd command, then I want them all to scp a file, etc. 例如,也许我有10个运行dd命令的服务器,然后我希望它们全部scp文件等。

My ultimate goal is to create a class for each system that keeps track of the information for the system in which it is tied to like IP address, logs, runtime, etc. But that class must be able to launch a system command and then return execution back to the caller while that system command runs, to followup with the result of the system command later. 我的最终目标是为每个系统创建一个类,用于跟踪与其绑定的系统的信息,如IP地址,日志,运行时等。但该类必须能够启动系统命令然后返回在系统命令运行时执行回执行调用程序,以便稍后跟随系统命令的结果。

My attempt is failing because I cannot send an instance method of a class over the pipe to the subprocess via pickle. 我的尝试失败了,因为我无法通过pickle将管道上的类的实例方法发送到子进程。 Those are not pickleable. 那些不是pickleable。 I therefore tried to fix it various ways but I can't figure it out. 因此,我试图以各种方式解决它,但我无法弄明白。 How can my code be patched to do this? 如何修补我的代码呢? What good is multiprocessing if you can't send over anything useful? 如果你不能发送任何有用的东西,多处理有什么用?

Is there any good documentation of multiprocessing being used with class instances? 是否有与类实例一起使用的多处理的良好文档? The only way I can get the multiprocessing module to work is on simple functions. 我可以使多处理模块工作的唯一方法是简单的功能。 Every attempt to use it within a class instance has failed. 在类实例中使用它的每次尝试都失败了。 Maybe I should pass events instead? 也许我应该通过事件呢? I don't understand how to do that yet. 我还不明白该怎么做。

import multiprocessing
import sys
import re

class ProcessWorker(multiprocessing.Process):
    """
    This class runs as a separate process to execute worker's commands in parallel
    Once launched, it remains running, monitoring the task queue, until "None" is sent
    """

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

    def run(self):
        """
        Overloaded function provided by multiprocessing.Process.  Called upon start() signal
        """
        proc_name = self.name
        print '%s: Launched' % (proc_name)
        while True:
            next_task_list = self.task_q.get()
            if next_task is None:
                # Poison pill means shutdown
                print '%s: Exiting' % (proc_name)
                self.task_q.task_done()
                break
            next_task = next_task_list[0]
            print '%s: %s' % (proc_name, next_task)
            args = next_task_list[1]
            kwargs = next_task_list[2]
            answer = next_task(*args, **kwargs)
            self.task_q.task_done()
            self.result_q.put(answer)
        return
# End of ProcessWorker class

class Worker(object):
    """
    Launches a child process to run commands from derived classes in separate processes,
    which sit and listen for something to do
    This base class is called by each derived worker
    """
    def __init__(self, config, index=None):
        self.config = config
        self.index = index

        # Launce the ProcessWorker for anything that has an index value
        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()
            print "Got here"
            # Process should be running and listening for functions to execute
        return

    def enqueue_process(target):  # No self, since it is a decorator
        """
        Used to place an command target from this class object into the task_q
        NOTE: Any function decorated with this must use fetch_results() to get the
        target task's result value
        """
        def wrapper(self, *args, **kwargs):
            self.task_q.put([target, args, kwargs]) # FAIL: target is a class instance method and can't be pickled!
        return wrapper

    def fetch_results(self):
        """
        After all processes have been spawned by multiple modules, this command
        is called on each one to retreive the results of the call.
        This blocks until the execution of the item in the queue is complete
        """
        self.task_q.join()                          # Wait for it to to finish
        return self.result_q.get()                  # Return the result

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

        # In here, I will launch a subprocess to run a  long-running system command
        # p = Popen(command), etc
        # p.wait(), etc
        return 

    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() 

Edit: I tried to move the ProcessWorker class and the creation of the multiprocessing queues outside of the Worker class and then tried to manually pickle the worker instance. 编辑:我试图移动ProcessWorker类并在Worker类之外创建多处理队列,然后尝试手动pickle工作器实例。 Even that doesn't work and I get an error 即使这不起作用,我得到一个错误

RuntimeError: Queue objects should only be shared between processes through inheritance RuntimeError:只应通过继承在进程之间共享队列对象

. But I am only passing references of those queues into the worker instance?? 但我只是将这些队列的引用传递给worker实例? I am missing something fundamental. 我遗漏了一些基本的东西。 Here is the modified code from the main section: 以下是主要部分的修改代码:

if __name__ == '__main__':
    config = ["some value", "something else"]
    index = 7
    workers = []
    for i in range(1):
        task_q = multiprocessing.JoinableQueue()
        result_q = multiprocessing.Queue()
        process_worker = ProcessWorker(task_q, result_q)
        worker = Worker(config, index, process_worker, task_q, result_q)
        something_to_look_at = pickle.dumps(worker) # FAIL:  Doesn't like queues??
        process_worker.start()
        worker.run_long_command("ls /")

So, the problem was that I was assuming that Python was doing some sort of magic that is somehow different from the way that C++/fork() works. 所以,问题在于我假设Python正在做某种与C ++ / fork()工作方式不同的魔术。 I somehow thought that Python only copied the class, not the whole program into a separate process. 我不知何故认为Python只复制了类,而不是将整个程序复制到一个单独的进程中。 I seriously wasted days trying to get this to work because all of the talk about pickle serialization made me think that it actually sent everything over the pipe. 我严重浪费了几天试图让这个工作,因为所有关于pickle序列化的讨论使我认为它实际上发送了所有东西通过管道。 I knew that certain things could not be sent over the pipe, but I thought my problem was that I was not packaging things up properly. 我知道某些东西不能通过管道发送,但我认为我的问题是我没有正确包装。

This all could have been avoided if the Python docs gave me a 10,000 ft view of what happens when this module is used. 如果Python文档给了我一个10,000英尺的视图,了解使用此模块时会发生什么,这一切都可以避免。 Sure, it tells me what the methods of multiprocess module does and gives me some basic examples, but what I want to know is what is the "Theory of Operation" behind the scenes! 当然,它告诉我多进程模块的方法是做什么的,并给我一些基本的例子,但我想知道的是幕后的“操作理论”是什么! Here is the kind of information I could have used. 这是我可以使用的那种信息。 Please chime in if my answer is off. 如果我的回答没有,请发信息。 It will help me learn. 它会帮助我学习。

When you run start a process using this module, the whole program is copied into another process. 当您使用此模块运行启动流程时,整个程序将复制到另一个流程中。 But since it is not the " __main__ " process and my code was checking for that, it doesn't fire off yet another process infinitely. 但是因为它不是“ __main__ ”进程而我的代码正在检查它,所以它不会无限启动另一个进程。 It just stops and sits out there waiting for something to do, like a zombie. 它只是停下来,坐在那里等待做某事,比如一个僵尸。 Everything that was initialized in the parent at the time of calling multiprocess.Process() is all set up and ready to go. 调用multiprocess.Process()时在父级中初始化的所有内容都已设置完毕并准备就绪。 Once you put something in the multiprocess.Queue or shared memory, or pipe, etc. (however you are communicating), then the separate process receives it and gets to work. 一旦你把东西放在multiprocess.Queue或共享内存,或管道等(但你正在沟通),然后单独的进程接收它并开始工作。 It can draw upon all imported modules and setup just as if it was the parent. 它可以在所有导入的模块和设置上绘制,就像它是父项一样。 However, once some internal state variables change in the parent or separate process, those changes are isolated. 但是,一旦某些内部状态变量在父进程或单独进程中发生更改,则这些更改将被隔离。 Once the process is spawned, it now becomes your job to keep them in sync if necessary, either through a queue, pipe, shared memory, etc. 一旦生成了该进程,现在就可以通过队列,管道,共享内存等方式在必要时使它们保持同步。

I threw out the code and started over, but now I am only putting one extra function out in the ProcessWorker , an "execute" method that runs a command line. 我抛弃了代码并重新开始,但现在我只在ProcessWorker中添加了一个额外的功能,一个运行命令行的“执行”方法。 Pretty simple. 很简单。 I don't have to worry about launching and then closing a bunch of processes this way, which has caused me all kinds of instability and performance issues in the past in C++. 我不必担心以这种方式启动然后关闭一堆进程,这在过去的C ++中引起了各种不稳定性和性能问题。 When I switched to launching processes at the beginning and then passing messages to those waiting processes, my performance improved and it was very stable. 当我在开始时切换到启动进程然后将消息传递给那些等待进程时,我的性能得到了改善并且非常稳定。

BTW, I looked at this link to get help, which threw me off because the example made me think that methods were being transported across the queues: http://www.doughellmann.com/PyMOTW/multiprocessing/communication.html The second example of the first section used "next_task()" that appeared (to me) to be executing a task received via the queue. 顺便说一句,我查看了这个链接以获得帮助,这让我失望,因为这个例子让我觉得方法是通过队列传输的: http//www.doughellmann.com/PyMOTW/multiprocessing/communication.html第二个例子第一部分使用“next_task()”出现(对我来说)执行通过队列接收的任务。

Instead of attempting to send a method itself (which is impractical), try sending a name of a method to execute. 尝试发送要执行的方法的名称 ,而不是尝试发送方法本身(这是不切实际的)。

Provided that each worker runs the same code, it's a matter of a simple getattr(self, task_name) . 如果每个worker都运行相同的代码,那就是一个简单的getattr(self, task_name)

I'd pass tuples (task_name, task_args) , where task_args were a dict to be directly fed to the task method: 我传递元组(task_name, task_args) ,其中task_args是一个直接输入任务方法的字典:

next_task_name, next_task_args = self.task_q.get()
if next_task_name:
  task = getattr(self, next_task_name)
  answer = task(**next_task_args)
  ...
else:
  # poison pill, shut down
  break

REF: https://stackoverflow.com/a/14179779 REF: https//stackoverflow.com/a/14179779

Answer on Jan 6 at 6:03 by David Lynch is not factually correct when he says that he was misled by http://www.doughellmann.com/PyMOTW/multiprocessing/communication.html . 大卫林奇1月6日6:03的回答当他说他被http://www.doughellmann.com/PyMOTW/multiprocessing/communication.html误导时,事实并非正确。

The code and examples provided are correct and work as advertised. 提供的代码和示例是正确的,并按照广告的方式工作。 next_task() is executing a task received via the queue -- try and understand what the Task.__call__() method is doing. next_task() 正在执行通过队列接收的任务 - 尝试并理解Task.__call__()方法正在做什么。

In my case what, tripped me up was syntax errors in my implementation of run() . 在我的情况下,绊倒我的是run()实现中的语法错误。 It seems that the sub-process will not report this and just fails silently -- leaving things stuck in weird loops! 似乎子流程不会报告这个并且只是默默地失败 - 让事情陷入奇怪的循环! Make sure you have some kind of syntax checker running eg Flymake/Pyflakes in Emacs. 确保在Emacs中运行某种语法检查程序,例如Flymake / Pyflakes。

Debugging via multiprocessing.log_to_stderr() F helped me narrow down the problem. 通过multiprocessing.log_to_stderr()调试F帮助我缩小了问题的范围。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM