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
여기서 multiprocessing.Queue보다는을 사용하는 것이 좋습니다 Manager. 를 사용 Manager하려면 완전히 새로운 프로세스를 생성 Queue해야합니다.
dano

1
@ dano : Queue () 객체를 사용하면 각 프로세스가 값을 반환 할 때 순서를 알 수 없습니다. 결과에 순서가 필요한 경우 다음 작업을 수행합니다. 정확히 어떤 출력이 어느 프로세스에서
나오는지

4
@Catbuilts 각 프로세스에서 튜플을 반환 할 수 있습니다. 하나의 값은 실제 반환 값이고 다른 하나는 프로세스의 고유 식별자입니다. 그러나 어떤 프로세스가 어떤 값을 반환하는지 알아야하는 이유가 궁금합니다. 프로세스에 대해 실제로 알아야하는 것이 입력 목록과 출력 목록 사이에 상관 관계가 있습니까? 이 경우 multiprocessing.Pool.map작업 항목 목록을 처리 하는 데 사용 하는 것이 좋습니다 .
dano

5
단일 인수 만있는 함수에 대한주의 사항 : 사용해야합니다 args=(my_function_argument, ). ,쉼표를 참고 하십시오! 그렇지 않으면 파이썬은 "위치 인수 누락"에 대해 불평합니다. 알아낼 10 분이 걸렸습니다. 또한 "프로세스 클래스"섹션 에서 수동 사용법을 확인하십시오 .
yuqli

2
@vartec multipriocessing.Manager () 사전을 사용하는 한 가지 단점은 반환하는 객체를 피클 (직렬화)하므로 피클 라이브러리에서 최대 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

풀이 작업자에게 작업을 어떻게 분배하는지 잘 모르겠습니다. 그들이 정말 빠르면 모두 같은 일꾼이 될 수 있을까요? 일관되게 발생합니까? 또한 지연을 추가하면?
Mark

또한 속도와 관련이 있다고 생각했지만 pool.map10 개 이상의 프로세스를 사용하여 1,000,000 범위를 공급 하면 최대 두 개의 다른 pid가 나타납니다.
zelusp

1
그럼 확실하지 않습니다. 나는 이것에 대해 별도의 질문을 여는 것이 흥미로울 것이라고 생각합니다.
Mark

각 프로세스에 다른 기능을 보내려는 경우 docs.python.org/3/library/…를 사용하십시오 pool.apply_async.
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!']

이 솔루션은 멀티 프로세싱 보다 적은 리소스 를 사용합니다.

  • 파이프
  • 적어도 하나의 자물쇠
  • 버퍼

또는 멀티 프로세싱 을 사용 하는 SimpleQueue

  • 파이프
  • 적어도 하나의 자물쇠

이러한 각 유형의 소스를 보는 것이 매우 유익합니다.


파이프를 전역 변수로 만들지 않고 가장 좋은 방법은 무엇입니까?
Nickpick

모든 전역 데이터와 코드를 주 함수에 넣고 동일하게 작동합니다. 그 질문에 대답합니까?
David Cullen

파이프에 새로운 값을 추가 (보내기)하기 전에 항상 파이프를 읽어야합니까?
Nickpick

+1, 정답입니다. 그러나 솔루션의 효율성이 높아지면 Pipe프로세스 당 하나 Queue를 모든 프로세스에 대해 하나씩 만드는 것이 좋습니다 . 그것이 모든 경우에 더 효율적인지 모르겠습니다.
sudo

2
이 응답은 반환 객체가 큰 경우 교착 상태를 유발합니다. 먼저 proc.join ()을 수행하는 대신 먼저 반환 값을 recv () 시도한 다음 조인을 시도합니다.
L. Pes

21

어떤 이유로 든 어디서나이 작업을 수행하는 방법에 대한 일반적인 예를 찾을 수 없었습니다 Queue(파이썬의 doc 예제는 여러 프로세스를 생성하지 않습니다). 그래서 10 번 시도한 후에 작업 한 내용은 다음과 같습니다.

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하위 프로세스의 리턴 값을 저장하는 데 사용할 수있는 차단 스레드 안전 큐입니다. 따라서 각 프로세스에 큐를 전달해야합니다. 여기에 덜 분명 뭔가는해야한다는 것입니다 get()전에 대기열에서 ES 또는 다른 큐가 가득하고 차단 다.joinProcess

객체 지향적 인 사람들을 위해 업데이트하십시오 (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
그렇습니다. 내 근로자는 모두 마무리합니다 (노동자 기능 내 루프가 종료되고 나중에 모든 근로자에 ​​대한 인쇄 명세서가 인쇄 됨). 조인은 아무것도하지 않습니다. Queue내 함수에서를 제거하면join()
Laurens Koppenol

@LaurensKoppenol 당신은 아마 전화 queue.put(ret)하기 전에 전화하지 p.start()않습니까? 이 경우 작업자 스레드가 queue.get()영원히 중단 됩니다. 주석 처리하는 동안 위의 스 니펫을 복사하여이를 복제 할 수 있습니다 queue.put(ret).
Matthew Moisen

이 답변을 편집 queue.get()했습니다 p.join(). 그것은 나를 위해 지금 작동합니다.
jfunk


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 바운드 작업을 위해 비워 둡니다. (I / O 바운드 작업은 스레드가 대부분 대기 중이므로 CPU 코어를 공유 할 수 있기 때문에 종종 스레드에 적합합니다.) Pool또한 청킹 최적화를 적용 합니다 .

(워커 메소드는 메소드 내에 중첩 될 수 없습니다. 처음에 호출하는 메소드 내에 워커 메소드를 정의 pool.map하여 모두 독립형으로 유지했지만 프로세스가 가져올 수 없었고 "AttributeError"가 발생했습니다. : 로컬 객체 outer_method..inner_method "를 선택할 수 없습니다. 자세한 내용은 여기를 참조하십시오 . 클래스 내부에있을 수 있습니다.)

(원래 질문에 인쇄가 'represent!'아닌 지정된 인쇄를 고맙게 time.sleep()생각하지 않으면 코드가 없을 때 일부 코드가 동시에 실행되고 있다고 생각했습니다.)


Py3 ProcessPoolExecutor은 또한 두 줄입니다 ( .map제너레이터를 반환하므로list() ) .

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

평범하게 Process es로 :

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필요한 모든이 put및 인 경우 사용하십시오 get. 첫 번째 루프는 두 번째 프로세스가 블로킹 queue.get호출을 하기 전에 모든 프로세스를 시작 합니다. 전화해야 할 이유가 없다고 생각 p.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의 답변을 약간 수정했습니다. (버텍 감사합니다! 대단한 트릭입니다)

이것은 또한 할 수 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.