web-dev-qa-db-ja.com

マルチプロセッサシステムで並列子プロセスを生成する方法

Pythonスクリプトを別のコントローラーとしてコントローラーとして使用したいPythonスクリプト。この2番目の64個の子プロセスPythonスクリプト。子スクリプトは次のように呼び出されます。

$ python create_graphs.py --name=NAME

nAMEはXYZ、ABC、NYUなどのようなものです.

親コントローラースクリプトで、リストから名前変数を取得します。

my_list = [ 'XYZ', 'ABC', 'NYU' ]

だから私の質問は、これらのプロセスを子供として生み出す最良の方法は何ですか?子の数を一度に64に制限したいので、ステータスを追跡する必要があります(子プロセスが終了したかどうか)。これにより、世代全体を効率的に実行し続けることができます。

サブプロセスパッケージの使用を検討しましたが、一度に1つの子しか生成しないため、拒否しました。最終的にマルチプロセッサパッケージを見つけましたが、スレッド全体とサブプロセスのドキュメントに圧倒されることは認めています。

現在、私のスクリプトではsubprocess.call一度に1つの子のみをスポーンし、次のようになります。

#!/path/to/python
import subprocess, multiprocessing, Queue
from multiprocessing import Process

my_list = [ 'XYZ', 'ABC', 'NYU' ]

if __== '__main__':
    processors = multiprocessing.cpu_count()

    for i in range(len(my_list)):
        if( i < processors ):
             cmd = ["python", "/path/to/create_graphs.py", "--name="+ my_list[i]]
             child = subprocess.call( cmd, Shell=False )

一度に64人の子供が生まれることが本当に欲しいです。他のstackoverflowの質問では、人々がキューを使用しているのを見ましたが、それはパフォーマンスに影響を与えるようです?

42
tatlar

探しているのは、マルチプロセッシングの プロセスプール クラスです。

import multiprocessing
import subprocess

def work(cmd):
    return subprocess.call(cmd, Shell=False)

if __== '__main__':
    count = multiprocessing.cpu_count()
    pool = multiprocessing.Pool(processes=count)
    print pool.map(work, ['ls'] * count)

そして、これはわかりやすくするための計算例です。以下は、N個のプロセスで10000個のタスクを分割します。ここで、NはCPUカウントです。プロセス数としてNoneを渡していることに注意してください。これにより、Poolクラスはプロセス数にcpu_countを使用します( reference

import multiprocessing
import subprocess

def calculate(value):
    return value * 10

if __== '__main__':
    pool = multiprocessing.Pool(None)
    tasks = range(10000)
    results = []
    r = pool.map_async(calculate, tasks, callback=results.append)
    r.wait() # Wait on the results
    print results
60
Nadia Alramli

ナディアとジムのコメントに基づいて、ここに私が思いついた解決策があります。それが最善の方法かどうかはわかりませんが、うまくいきます。 Matlabなどのサードパーティアプリを使用する必要があるため、呼び出される元の子スクリプトはシェルスクリプトである必要があります。だから、Pythonから取り出してbashでコーディングしなければなりませんでした。

import sys
import os
import multiprocessing
import subprocess

def work(staname):
    print 'Processing station:',staname
    print 'Parent process:', os.getppid()
    print 'Process id:', os.getpid()
    cmd = [ "/bin/bash" "/path/to/executable/create_graphs.sh","--name=%s" % (staname) ]
    return subprocess.call(cmd, Shell=False)

if __== '__main__':

    my_list = [ 'XYZ', 'ABC', 'NYU' ]

    my_list.sort()

    print my_list

    # Get the number of processors available
    num_processes = multiprocessing.cpu_count()

    threads = []

    len_stas = len(my_list)

    print "+++ Number of stations to process: %s" % (len_stas)

    # run until all the threads are done, and there is no data left

    for list_item in my_list:

        # if we aren't using all the processors AND there is still data left to
        # compute, then spawn another thread

        if( len(threads) < num_processes ):

            p = multiprocessing.Process(target=work,args=[list_item])

            p.start()

            print p, p.is_alive()

            threads.append(p)

        else:

            for thread in threads:

                if not thread.is_alive():

                    threads.remove(thread)

これは合理的な解決策のように思えますか? Jimのwhileループ形式を使用しようとしましたが、スクリプトは何も返しませんでした。なぜそうなるのか分かりません。 「for」ループの代わりにジムの「while」ループを使用してスクリプトを実行した場合の出力は次のとおりです。

hostname{me}2% controller.py 
['ABC', 'NYU', 'XYZ']
Number of processes: 64
+++ Number of stations to process: 3
hostname{me}3%

「for」ループで実行すると、より意味のあるものが得られます。

hostname{me}6% controller.py 
['ABC', 'NYU', 'XYZ']
Number of processes: 64
+++ Number of stations to process: 3
Processing station: ABC
Parent process: 1056
Process id: 1068
Processing station: NYU
Parent process: 1056
Process id: 1069
Processing station: XYZ
Parent process: 1056
Process id: 1071
hostname{me}7%

だからこれは機能し、私は幸せです。しかし、私が使用している「for」ループの代わりに、ジムの「while」スタイルのループを使用できない理由がまだわかりません。すべての助けをありがとう-stackoverflow @の知識の幅広さに感銘を受けました。

2
tatlar

サブプロセスを使用して独自のソリューションを展開するのではなく、間違いなく multiprocessing を使用します。

1
Aaron Maenpaa

アプリケーションからデータを取得する場合を除き、キューは必要ないと思います(データが必要な場合は、とにかくデータベースに追加する方が簡単かもしれません)

しかし、サイズのためにこれを試してください:

create_graphs.pyスクリプトの内容をすべて「create_graphs」という関数に入れます

import threading
from create_graphs import create_graphs

num_processes = 64
my_list = [ 'XYZ', 'ABC', 'NYU' ]

threads = []

# run until all the threads are done, and there is no data left
while threads or my_list:

    # if we aren't using all the processors AND there is still data left to
    # compute, then spawn another thread
    if (len(threads) < num_processes) and my_list:
        t = threading.Thread(target=create_graphs, args=[ my_list.pop() ])
        t.setDaemon(True)
        t.start()
        threads.append(t)

    # in the case that we have the maximum number of threads check if any of them
    # are done. (also do this when we run out of data, until all the threads are done)
    else:
        for thread in threads:
            if not thread.isAlive():
                threads.remove(thread)

これにより、プロセッサよりもスレッドが1つ少なくなることがわかっています。これはおそらく良いことです。スレッド、ディスクI/O、その他のコンピュータでの処理をプロセッサに任せます。最後のコアを使用することに決めた場合は、1つ追加するだけです

編集:my_listの目的を誤って解釈した可能性があると思います。スレッドを追跡するためにmy_listは必要ありません(スレッドはすべてthreadsリスト内のアイテムによって参照されるため)。しかし、これはプロセスの入力を供給する素晴らしい方法です-または、さらに良い方法:ジェネレーター関数を使用してください;)

my_listおよびthreadsの目的

my_listは、関数で処理する必要があるデータを保持します
threadsは、現在実行中のスレッドの単なるリストです

whileループは2つのことを行い、新しいスレッドを開始してデータを処理し、実行中のスレッドがあるかどうかを確認します。

したがって、(a)処理するデータが増えるか、(b)実行が終了しないスレッドがある限り、実行を継続するようにプログラムする必要があります。 両方のリストが空になると、Falseと評価され、whileループが終了します

1
Jiaaro