multiprocessing.Processに渡された関数の戻り値を回復するにはどうすればよいですか?


190

以下のコード例では、関数の戻り値を回復したいと思いますworker。これを行うにはどうすればよいですか?この値はどこに保存されますか?

コード例:

import multiprocessing

def worker(procnum):
    '''worker function'''
    print str(procnum) + ' represent!'
    return procnum


if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print jobs

出力:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[<Process(Process-1, stopped)>, <Process(Process-2, stopped)>, <Process(Process-3, stopped)>, <Process(Process-4, stopped)>, <Process(Process-5, stopped)>]

に保存されているオブジェクトに関連する属性が見つからないようですjobs

回答:


189

共有変数を使用て通信します。たとえば、次のようになります。

import multiprocessing

def worker(procnum, return_dict):
    '''worker function'''
    print str(procnum) + ' represent!'
    return_dict[procnum] = procnum


if __name__ == '__main__':
    manager = multiprocessing.Manager()
    return_dict = manager.dict()
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,return_dict))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print return_dict.values()

46
here multiprocessing.Queueではなくを使用することをお勧めしManagerます。を使用するにManagerは、まったく新しいプロセスを生成する必要Queueがあります。
ダノ

1
@dano:Queue()オブジェクトを使用する場合、各プロセスが値を返す順序を確認できません。結果の順序が必要な場合は、次の作業を行うためです。どの出力がどのプロセスから正確にどこにあるのかをどのように確認できますか
Catbuilts

4
@Catbuilts各プロセスからタプルを返すことができます。1つの値は実際の戻り値で、もう1つの値はプロセスからの一意の識別子です。しかし、なぜ、どのプロセスがどの値を返すのかを知る必要があるのでしょうか。その場合、プロセスについて実際に知っておく必要があること、または入力のリストと出力のリストを関連付ける必要がありますか?その場合は、を使用multiprocessing.Pool.mapしてワークアイテムのリストを処理することをお勧めします。
ダノ

5
引数が1つだけの関数の警告:を使用する必要がありますargs=(my_function_argument, )。ここの,コンマに注意してください!それ以外の場合、Pythonは「位置引数がない」と文句を言います。理解するのに10分かかった。手動の使用法も確認してください(「プロセスクラス」セクションの下)。
yuqli

2
@vartec multipriocessing.Manager()ディクショナリを使用することの1つの欠点は、返すオブジェクトをピクル(シリアライズ)することです。そのため、オブジェクトが返す最大2GiBサイズのピクルライブラリによってボトルネックが発生します。返すオブジェクトのシリアル化を回避するこれを行う他の方法はありますか?
hirschme

68

@sega_saiが提案するアプローチの方が良いと思います。しかし、実際にはコード例が必要なので、次のようにします。

import multiprocessing
from os import getpid

def worker(procnum):
    print('I am number %d in process %d' % (procnum, getpid()))
    return getpid()

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes = 3)
    print(pool.map(worker, range(5)))

戻り値を出力します:

I am number 0 in process 19139
I am number 1 in process 19138
I am number 2 in process 19140
I am number 3 in process 19139
I am number 4 in process 19140
[19139, 19138, 19140, 19139, 19140]

map(Python 2ビルトイン)に慣れている場合、これはそれほど難しくありません。それ以外の場合は、sega_Saiのリンクご覧ください。

必要なコードが少ないことに注意してください。(また、プロセスの再利用方法にも注意してください)。


1
getpid()がすべて同じ値を返す理由は何ですか?私はPython3を実行しています
zelusp

Poolがどのようにタスクをワーカーに分散するのかわかりません。もし彼らが本当に速いのなら、多分彼らはすべて同じ労働者になるかもしれませんか?それは一貫して起こりますか?また、遅延を追加した場合はどうなりますか?
マーク

また、速度に関連するものだと思っていましたが、pool.map10を超えるプロセスを使用して1,000,000の範囲をフィードすると、最大で2つの異なるpidが表示されます。
zelusp

1
その後、私にはわかりません。これについて別の質問をするのは面白いと思います。
マーク

各プロセスに異なる関数を送信したい場合は、次を使用しpool.apply_asyncます。docs.python.org
Kyle

24

この例は、multiprocessing.Pipeインスタンスのリストを使用して、任意の数のプロセスから文字列を返す方法を示しています。

import multiprocessing

def worker(procnum, send_end):
    '''worker function'''
    result = str(procnum) + ' represent!'
    print result
    send_end.send(result)

def main():
    jobs = []
    pipe_list = []
    for i in range(5):
        recv_end, send_end = multiprocessing.Pipe(False)
        p = multiprocessing.Process(target=worker, args=(i, send_end))
        jobs.append(p)
        pipe_list.append(recv_end)
        p.start()

    for proc in jobs:
        proc.join()
    result_list = [x.recv() for x in pipe_list]
    print result_list

if __name__ == '__main__':
    main()

出力:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
['0 represent!', '1 represent!', '2 represent!', '3 represent!', '4 represent!']

このソリューションは、マルチプロセッシングよりも少ないリソースを使用します。

  • パイプ
  • 少なくとも1つのロック
  • バッファ
  • スレッド

または使用するmultiprocessing.SimpleQueue

  • パイプ
  • 少なくとも1つのロック

これらの各タイプのソースを確認することは非常に有益です。


パイプをグローバル変数にせずにそれを行う最良の方法は何でしょうか?
Nickpick 2016年

私はすべてのグローバルデータとコードをメイン関数に入れ、同じように機能します。それはあなたの質問に答えますか?
David Cullen

新しい値をパイプに追加(送信)する前に、パイプを常に読み取る必要がありますか?
Nickpick

+1、良い答え。しかし、より効率的なソリューションに関しては、トレードオフは、Pipeプロセスごとに1つとQueueすべてのプロセスに対して1つを作成することです。それがすべてのケースでより効率的になるかどうかはわかりません。
sudo 2017

2
返されるオブジェクトが大きい場合、この回答によりデッドロックが発生します。最初にproc.join()を実行する代わりに、最初に戻り値をrecv()してから結合を行います。
L.ペス

22

どういうわけか、Queueどこででもこれを行う方法の一般的な例を見つけることができませんでした(Pythonのドキュメントの例でも複数のプロセスを生成しません)。

def add_helper(queue, arg1, arg2): # the func called in child processes
    ret = arg1 + arg2
    queue.put(ret)

def multi_add(): # spawns child processes
    q = Queue()
    processes = []
    rets = []
    for _ in range(0, 100):
        p = Process(target=add_helper, args=(q, 1, 2))
        processes.append(p)
        p.start()
    for p in processes:
        ret = q.get() # will block
        rets.append(ret)
    for p in processes:
        p.join()
    return rets

Queue子プロセスからの戻り値を格納するために使用できる、スレッドセーフなブロッキングキューです。したがって、各プロセスにキューを渡す必要があります。ここでそれほど明白ではないことは、esまたはキューがいっぱいになってすべてをブロックするget()前に、キューから削除する必要がjoinあるProcessことです。

オブジェクト指向の人向けの更新(Python 3.4でテスト済み):

from multiprocessing import Process, Queue

class Multiprocessor():

    def __init__(self):
        self.processes = []
        self.queue = Queue()

    @staticmethod
    def _wrapper(func, queue, args, kwargs):
        ret = func(*args, **kwargs)
        queue.put(ret)

    def run(self, func, *args, **kwargs):
        args2 = [func, self.queue, args, kwargs]
        p = Process(target=self._wrapper, args=args2)
        self.processes.append(p)
        p.start()

    def wait(self):
        rets = []
        for p in self.processes:
            ret = self.queue.get()
            rets.append(ret)
        for p in self.processes:
            p.join()
        return rets

# tester
if __name__ == "__main__":
    mp = Multiprocessor()
    num_proc = 64
    for _ in range(num_proc): # queue up multiple tasks running `sum`
        mp.run(sum, [1, 2, 3, 4, 5])
    ret = mp.wait() # get all results
    print(ret)
    assert len(ret) == num_proc and all(r == 15 for r in ret)

18

Processusing から値を取得する方法を探している他の人のためにQueue

import multiprocessing

ret = {'foo': False}

def worker(queue):
    ret = queue.get()
    ret['foo'] = True
    queue.put(ret)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    queue.put(ret)
    p = multiprocessing.Process(target=worker, args=(queue,))
    p.start()
    print queue.get()  # Prints {"foo": True}
    p.join()

1
ワーカープロセスのキューに何かを入れると、参加に到達しません。どうやってこれが来るのか?
Laurens Koppenol

@LaurensKoppenolメインコードがp.join()で永久にハングし、継続しないことを意味しますか?プロセスに無限ループがありますか?
Matthew Moisen

4
はい、それは無限にそこにぶら下がっています。私のワーカーはすべて終了します(すべてのワーカーについて、ワーカー関数内のループが終了し、後でprintステートメントが出力されます)。結合は何もしません。私は削除した場合Queue、私の機能から、それは私が通過させないjoin()
ローレンスKoppenol

@LaurensKoppenol電話をかけるqueue.put(ret)前に電話をかけていp.start()ませんか?その場合、ワーカースレッドはqueue.get()永久にハングします。コメントアウトするときに上記のスニペットをコピーすることで、これを複製できqueue.put(ret)ます。
Matthew Moisen 2017

私はこの回答を編集しましqueue.get()p.join()。それは今私のために動作します。
jfunk 2017年


10

exit組み込みを使用して、プロセスの終了コードを設定できます。exitcodeプロセスの属性から取得できます。

import multiprocessing

def worker(procnum):
    print str(procnum) + ' represent!'
    exit(procnum)

if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    result = []
    for proc in jobs:
        proc.join()
        result.append(proc.exitcode)
    print result

出力:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

4
このアプローチは混乱を招く可能性があることに注意してください。プロセスは通常、エラーなしで完了すると、終了コード0で終了します。システムプロセスの終了コードを監視しているものがある場合、これらがエラーとして報告される場合があります。
ferrouswheel

1
エラー時に親プロセスで例外を発生させたい場合に最適です。
crizCraig


3

上記からコピーした最も単純な例を単純化して、Py3.6で動作すると思います。最も簡単ですmultiprocessing.Pool

import multiprocessing
import time

def worker(x):
    time.sleep(1)
    return x

pool = multiprocessing.Pool()
print(pool.map(worker, range(10)))

たとえばを使用して、プール内のプロセス数を設定できますPool(processes=5)。ただし、デフォルトではCPUカウントになるため、CPUにバインドされたタスクの場合は空白のままにします。(スレッドはほとんど待機しているため、CPUコアを共有できるため、I / Oバウンドタスクは多くの場合、スレッドに適しています。)チャンキングの最適化Poolも適用します。

(ワーカーメソッドをメソッド内にネストすることはできないことに注意してください。ワーカーメソッドを最初にに呼び出すメソッド内に定義し、pool.mapすべてを自己完結型に保ちましたが、プロセスはそれをインポートできず、 "AttributeError :ローカルオブジェクトouter_method..inner_method "をピクルできません。詳細はこちらは、クラス内ですることができます)。

(元の質問では印刷'represent!'ではなく印刷が指定されていましたがtime.sleep()、それがなければ、いくつかのコードが同時に実行されていなかったと思いました。)


Py3 ProcessPoolExecutorも2行です(.mapジェネレーターを返すので、必要ですlist()):

from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as executor:
    print(list(executor.map(worker, range(10))))

プレーンProcessesの場合:

import multiprocessing
import time

def worker(x, queue):
    time.sleep(1)
    queue.put(x)

queue = multiprocessing.SimpleQueue()
tasks = range(10)

for task in tasks:
    multiprocessing.Process(target=worker, args=(task, queue,)).start()

for _ in tasks:
    print(queue.get())

SimpleQueueが必要な場合のみ使用してください。最初のループは、2番目のループがブロッキング呼び出しを行う前に、すべてのプロセスを開始します。電話する理由もないと思います。putgetqueue.getp.join()


2

簡単な解決策:

import multiprocessing

output=[]
data = range(0,10)

def f(x):
    return x**2

def handler():
    p = multiprocessing.Pool(64)
    r=p.map(f, data)
    return r

if __name__ == '__main__':
    output.append(handler())

print(output[0])

出力:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

2

Python 3を使用しconcurrent.futures.ProcessPoolExecutorている場合は、便利な抽象化として使用できます。

from concurrent.futures import ProcessPoolExecutor

def worker(procnum):
    '''worker function'''
    print(str(procnum) + ' represent!')
    return procnum


if __name__ == '__main__':
    with ProcessPoolExecutor() as executor:
        print(list(executor.map(worker, range(5))))

出力:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

0

関数からエラーコードを取得する必要があるため、vartecの回答を少し変更しました。(vertecに感謝!!!それは素晴らしいトリックです)

これはaでも実行できmanager.listますが、dictに入れてリストを保存する方が良いと思います。このようにして、リストが入力される順序がわからないため、関数と結果を保持します。

from multiprocessing import Process
import time
import datetime
import multiprocessing


def func1(fn, m_list):
    print 'func1: starting'
    time.sleep(1)
    m_list[fn] = "this is the first function"
    print 'func1: finishing'
    # return "func1"  # no need for return since Multiprocess doesnt return it =(

def func2(fn, m_list):
    print 'func2: starting'
    time.sleep(3)
    m_list[fn] = "this is function 2"
    print 'func2: finishing'
    # return "func2"

def func3(fn, m_list):
    print 'func3: starting'
    time.sleep(9)
    # if fail wont join the rest because it never populate the dict
    # or do a try/except to get something in return.
    raise ValueError("failed here")
    # if we want to get the error in the manager dict we can catch the error
    try:
        raise ValueError("failed here")
        m_list[fn] = "this is third"
    except:
        m_list[fn] = "this is third and it fail horrible"
        # print 'func3: finishing'
        # return "func3"


def runInParallel(*fns):  # * is to accept any input in list
    start_time = datetime.datetime.now()
    proc = []
    manager = multiprocessing.Manager()
    m_list = manager.dict()
    for fn in fns:
        # print fn
        # print dir(fn)
        p = Process(target=fn, name=fn.func_name, args=(fn, m_list))
        p.start()
        proc.append(p)
    for p in proc:
        p.join()  # 5 is the time out

    print datetime.datetime.now() - start_time
    return m_list, proc

if __name__ == '__main__':
    manager, proc = runInParallel(func1, func2, func3)
    # print dir(proc[0])
    # print proc[0]._name
    # print proc[0].name
    # print proc[0].exitcode

    # here you can check what did fail
    for i in proc:
        print i.name, i.exitcode  # name was set up in the Process line 53

    # here will only show the function that worked and where able to populate the 
    # manager dict
    for i, j in manager.items():
        print dir(i)  # things you can do to the function
        print i, j
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.