Як я можу відновити повернене значення функції, переданої в багатопроцесорний процес.Процес?


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робитиме.
Дано

1
@dano: Цікаво, що якщо ми використовуємо об’єкт Queue (), ми не можемо впевнитись у порядку, коли кожен процес повертає значення. Я маю на увазі, якщо нам потрібен порядок в результаті, щоб зробити наступну роботу. Як ми могли переконатися, де саме, який вихід із якого процесу
Catbuilts

4
@Catbuilts Ви можете повернути кортеж з кожного процесу, де одне значення - це фактичне значення, яке вам важливо, а інше - унікальний ідентифікатор процесу. Але мені також цікаво, чому потрібно знати, який процес повертає, яке значення. Якщо це те, що вам насправді потрібно знати про процес, чи вам потрібно співвідносити між вашим списком входів та списком результатів? У такому випадку я рекомендую використовувати multiprocessing.Pool.mapдля обробки свого списку робочих предметів.
Дано

5
застереження для функцій лише з одним аргументом : слід використовувати args=(my_function_argument, ). Зверніть увагу на ,коску тут! Або ще Python поскаржиться на "відсутні аргументи позиції". Мені потрібно було 10 хвилин, щоб розібратися. Також перевірте використання вручну (у розділі "клас класу процесів").
yuqli

2
@vartec недоліком використання словника multipriocessing.Manager () є те, що підбирає (серіалізує) об'єкт, який він повертає, тому у нього є вузьке місце, яке надається бібліотекою солінь розміром не більше 2 Гбіб для повернення об'єкта. Чи є якийсь інший спосіб зробити це, уникаючи серіалізації об'єкта, що повертається?
hirschme

67

Я думаю, що підхід, запропонований @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.mapдіапазон в 1 000 000, використовуючи більш ніж 10 процесів, я бачу щонайбільше два різних прища.
zelusp

1
Тоді я не впевнений. Я думаю, було б цікаво відкрити для цього окреме питання.
Марк

Якщо ви хочете надіслати різні функції для кожного процесу, використовуйте pool.apply_async: docs.python.org/3/library/…
Kyle

24

У цьому прикладі показано, як використовувати список багатопроцесорних процесів. Для повернення рядків з довільної кількості процесів:

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!']

Це рішення використовує менше ресурсів , ніж multiprocessing.Queue , який використовує

  • a Труба
  • принаймні один замок
  • буфер
  • нитка

або багатопроцесорна.SimpleQueue, яка використовує

  • a Труба
  • принаймні один замок

Дуже повчально дивитись на джерело кожного з цих типів.


Що було б найкращим способом зробити це, не зробивши труби глобальною змінною?
Нікпік,

Я ставлю всі глобальні дані та код до основної функції, і вона працює однаково. Це відповідає на ваше запитання?
Девід Каллен

чи потрібно завжди прочитати трубу, перш ніж до неї можна буде додати (надіслати) якесь нове значення?
Нікпік

+1, хороша відповідь. Але щодо того, що рішення є більш ефективним, компроміс полягає в тому, що ви робите один Pipeза процес проти одного Queueдля всіх процесів. Я не знаю, чи це в кінцевому випадку виявляється більш ефективним.
судо

2
Ця відповідь викликає тупик, якщо об'єкт, що повертається, великий. Замість того, щоб робити proc.join (), спершу я спробую recv () повернути значення, а потім зробити приєднання.
Л. Пес

21

Чомусь я не зміг знайти загальний приклад того, як це зробити з Queueбудь-якого місця (навіть приклади док-файлів Python не породили декілька процесів), ось ось що я працював після 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()з черги перед вами 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

Для всіх, хто шукає, як отримати значення від Processвикористання 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
коли я щось ставлю до черги в процесі роботи, моє приєднання ніколи не досягається. Будь-яка ідея, як це може вийти?
Лоренс Коппенол

@LaurensKoppenol Ви маєте на увазі, що ваш основний код постійно висить на p.join () і ніколи не продовжується? Чи має ваш процес нескінченний цикл?
Меттью Мойсен

4
Так, він там висить нескінченно. Мої працівники все закінчують (цикл у робочій функції закінчується, друкується виписка після цього для всіх працівників). Приєднання нічого не робить. Якщо я вийму Queueз своєї функції, це дозволить мені пройтиjoin()
Лоренс Коппенол

@LaurensKoppenol Ви, можливо, не дзвонили queue.put(ret)до дзвінка p.start()? У цьому випадку робоча нитка зависне queue.get()назавжди. Ви можете скопіювати це, скопіювавши мій фрагмент вище, коментуючи його queue.put(ret).
Меттью Мойсен

Я відредагував цю відповідь, queue.get()має статися до цього p.join(). Це зараз працює для мене.
jfunk

12

Здається, що ви повинні використовувати клас multiprocessing.Pool і використовувати методи .apply () .apply_async (), map ()

http://docs.python.org/library/multiprocessing.html?highlight=pool#multiprocessing.pool.AsyncResult


У мене є tensorflow код, для якого багатопроцесорний басейн буде висіти, але не багатопроцесорний.Процес
Le

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, якщо вони завершено без помилок. Якщо у вас є що-небудь моніторинг кодів виходу з системних процесів, ви можете бачити ці повідомлення як помилки.
чорне колесо

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). Однак він за замовчуванням нараховує кількість процесорів, тому залиште його порожнім для завдань, пов'язаних з процесором. (Завдання, пов'язані з входом / виводом, часто так чи інакше відповідають потокам, оскільки потоки в основному чекають, щоб вони могли мати спільне ядро ​​процесора.) PoolТакож застосовується оптимізація блокування .

(Зверніть увагу, що метод робочого не може бути вкладений в метод. Я спочатку визначив свій метод робітника всередині методу, який робить виклик pool.map, щоб він залишався самостійним, але потім процеси не змогли його імпортувати, і кинув "AttributeError : Неможливо вибрати місцевий об’єкт external_method..inner_method ". Більше тут . Це може бути всередині класу.)

(Цінуйте оригінальний питання , вказаний при друку , 'represent!'а не time.sleep(), але без нього я думав деякий код працює одночасно , коли його не було.)


У Py3's ProcessPoolExecutorтакож є два рядки ( .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якщо все, що вам потрібно, це 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але я думаю, що краще мати його в диктанті та зберігати список у ньому. Таким чином, ми зберігаємо функцію та результати, оскільки не можемо бути впевнені в порядку, у якому список буде заповнений.

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.