web-dev-qa-db-ja.com

Pythonのクラスインスタンスでマルチプロセッシングを使用する方法は?

私はクラスを作成しようとしていますが、別のプロセスを実行して長時間かかる作業を行い、これらの束をメインモジュールから起動し、すべてが完了するのを待ちます。プロセスを作成して破棄するのではなく、プロセスを一度起動してから、実行することをやり続けたいと思います。たとえば、ddコマンドを実行している10台のサーバーがあり、それらすべてにファイルをscpさせたい場合など。

私の最終的な目標は、IPアドレス、ログ、ランタイムなどのように関連付けられているシステムの情報を追跡する各システムのクラスを作成することです。しかし、そのクラスはシステムコマンドを起動し、システムコマンドの実行中に呼び出し元に戻って実行し、後でシステムコマンドの結果を追跡します。

ピクルを介してサブプロセスにパイプ経由でクラスのインスタンスメソッドを送信できないため、私の試みは失敗しています。それらは漬物ではありません。したがって、さまざまな方法で修正しようとしましたが、理解できません。これを行うためにコードにパッチを適用するにはどうすればよいですか?有用なものを送信できない場合、マルチプロセッシングはどのように役立ちますか?

クラスインスタンスで使用されているマルチプロセッシングの適切なドキュメントはありますか?マルチプロセッシングモジュールを機能させる唯一の方法は、単純な関数を使用することです。クラスインスタンス内でそれを使用する試みはすべて失敗しました。代わりにイベントを渡す必要がありますか?私はまだそれを行う方法を理解していません。

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

編集:ProcessWorkerクラスとマルチプロセッシングキューの作成をWorkerクラスの外に移動してから、ワーカーインスタンスを手動でpickleしようとしました。それでも機能せず、エラーが発生します

RuntimeError:キューオブジェクトは、継承を通じてプロセス間でのみ共有する必要があります

。しかし、私はそれらのキューの参照をワーカーインスタンスに渡すだけですか??基本的な何かが欠けています。以下は、メインセクションから変更されたコードです。

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 /")
23
David Lynch

メソッド自体を送信する(実際的ではありません)のではなく、実行するメソッドのnameを送信してみてください。

各ワーカーが同じコードを実行するのであれば、単純なgetattr(self, task_name)の問題です。

タプルを渡す(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
8
9000

だから、問題はPythonがC++/fork()の動作とは異なる何らかの魔法をしていると仮定していたことでした。何らかの理由でPythonは、プログラム全体ではなく、クラスのみをコピーして別のプロセスにコピーしました。pickleのシリアル化に関するすべての話が、実際にすべてをパイプ経由で送信していると思うようになったので、これを機能させるために何日も無駄にしました。特定の物をパイプで送ることができないことを知っていましたが、問題は適切に梱包していないことだと思いました。

Python docsがこのモジュールが使用されたときに何が起こるかについて10,000フィートのビューを提供してくれたなら、これはすべて回避できたでしょう。確かに、マルチプロセスモジュールのメソッドが基本的な例ですが、私が知りたいのは、舞台裏の「操作の理論」です!ここに私が使用できたような情報があります。答えがオフの場合はチャイムしてください。

このモジュールを使用してプロセスを開始すると、プログラム全体が別のプロセスにコピーされます。しかし、それは「__main__ "プロセスと私のコードがそれをチェックしていましたが、それはさらに別のプロセスを無限に起動しません。ただ停止し、ゾンビのような何かを待ってそこに座っています。その時に親で初期化されたすべてmultiprocess.Process()の呼び出しはすべて設定されており、準備ができています。multiprocess.Queueまたは共有メモリ、またはパイプなどに何かを入れると(通信している場合)、別のプロセスがそれを受け取り、インポートされたすべてのモジュールを使用して、親であるかのようにセットアップできますが、親または別のプロセスで内部状態変数が変更されると、それらの変更は分離されます。必要に応じて、キュー、パイプ、共有メモリなどを介して、それらの同期を維持します。

コードを捨てて最初からやり直しましたが、コマンドラインを実行する「実行」メソッドであるProcessWorkerに追加の関数を1つだけ追加しています。ものすごく単純。この方法で一連のプロセスを起動して閉じることを心配する必要はありません。これにより、過去にC++であらゆる種類の不安定性とパフォーマンスの問題が発生しました。最初にプロセスを起動し、それらの待機プロセスにメッセージを渡すように切り替えたとき、パフォーマンスが向上し、非常に安定しました。

ところで、私は助けを得るためにこのリンクを見ましたが、メソッドがキューを越えて移送されていると考えさせられたので、私はそれを思いとどまらせました: http://www.doughellmann.com/PyMOTW/multiprocessing/communication。 html 最初のセクションの2番目の例では、キューを介して受信したタスクを実行するために(私には)表示された「next_task()」を使用しました。

21
David Lynch

参照: https://stackoverflow.com/a/14179779

デビッド・リンチによる1月6日6時3分解答は、 http://www.doughellmann.com/PyMOTW /multiprocessing/communication.html

提供されているコードと例は正しく、広告どおりに機能します。 next_task()isキュー経由で受信したタスクを実行-Task.__call__()メソッドが何をしているのかを理解しよう。

私の場合は、run()の実装での構文エラーでした。サブプロセスはこれを報告せず、静かに失敗するようです-奇妙なループで物事がスタックしたままにします!何らかの構文チェッカーが実行されていることを確認してください。 EmacsのFlymake/Pyflakes。

multiprocessing.log_to_stderr() Fを介したデバッグは、問題を絞り込むのに役立ちました。

0
Sawan