Многопроцессорность: как использовать Pool.map для функции, определенной в классе?

Когда я запускаю что-то вроде:

from multiprocessing import Pool

p = Pool(5)
def f(x):
     return x*x

p.map(f, [1,2,3])

он отлично работает. Однако, полагая это как функцию класса:

class calculate(object):
    def run(self):
        def f(x):
            return x*x

        p = Pool()
        return p.map(f, [1,2,3])

cl = calculate()
print cl.run()

Дает мне следующую ошибку:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/sw/lib/python2.6/threading.py", line 532, in __bootstrap_inner
    self.run()
  File "/sw/lib/python2.6/threading.py", line 484, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/sw/lib/python2.6/multiprocessing/pool.py", line 225, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Я видел сообщение от Alex Martelli, занимающееся той же проблемой, но это было недостаточно ясно.

Ответы

Ответ 1

Меня также раздражали ограничения на то, какие функции pool.map мог бы принять. Я написал следующее, чтобы обойти это. Кажется, он работает, даже для рекурсивного использования parmap.

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(pipe,x):
        pipe.send(f(x))
        pipe.close()
    return fun

def parmap(f,X):
    pipe=[Pipe() for x in X]
    proc=[Process(target=spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]
    [p.start() for p in proc]
    [p.join() for p in proc]
    return [p.recv() for (p,c) in pipe]

if __name__ == '__main__':
    print parmap(lambda x:x**x,range(1,5))

Ответ 2

Я не мог использовать коды, опубликованные до сих пор, потому что коды, использующие "multiprocessing.Pool", не работают с лямбда-выражениями, а коды, не использующие "multiprocessing.Pool", порождают столько процессов, сколько есть рабочих элементов.

Я адаптировал код s.t. он порождает предопределенное количество работников и выполняет только итерацию через входной список, если существует незанятый рабочий. Я также включил режим "демона" для рабочих s.t. ctrl-c работает как ожидалось.

import multiprocessing


def fun(f, q_in, q_out):
    while True:
        i, x = q_in.get()
        if i is None:
            break
        q_out.put((i, f(x)))


def parmap(f, X, nprocs=multiprocessing.cpu_count()):
    q_in = multiprocessing.Queue(1)
    q_out = multiprocessing.Queue()

    proc = [multiprocessing.Process(target=fun, args=(f, q_in, q_out))
            for _ in range(nprocs)]
    for p in proc:
        p.daemon = True
        p.start()

    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [q_in.put((None, None)) for _ in range(nprocs)]
    res = [q_out.get() for _ in range(len(sent))]

    [p.join() for p in proc]

    return [x for i, x in sorted(res)]


if __name__ == '__main__':
    print(parmap(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8]))

Ответ 3

Многопроцессорная обработка и травление нарушены и ограничены, если вы не выйдете за пределы стандартной библиотеки.

Если вы используете pathos.multiprocesssing multiprocessing pathos.multiprocesssing, вы можете напрямую использовать классы и методы классов в многопроцессорных функциях map. Это потому, что dill используется вместо pickle или cPickle, и dill может сериализовать практически все в Python.

pathos.multiprocessing также предоставляет асинхронную функцию отображения... и может map функции с несколькими аргументами (например, map(math.pow, [1,2,3], [4,5,6]))

Смотрите обсуждения: что могут делать мультипроцессор и укроп вместе?

и: http://matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization

Он даже обрабатывает код, который вы написали изначально, без изменений и от интерпретатора. Зачем делать что-то еще более хрупкое и специфичное для одного случая?

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> class calculate(object):
...  def run(self):
...   def f(x):
...    return x*x
...   p = Pool()
...   return p.map(f, [1,2,3])
... 
>>> cl = calculate()
>>> print cl.run()
[1, 4, 9]

Получите код здесь: https://github.com/uqfoundation/pathos

И, просто чтобы показать немного больше, что он может сделать:

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> 
>>> p = Pool(4)
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> x = [0,1,2,3]
>>> y = [4,5,6,7]
>>> 
>>> p.map(add, x, y)
[4, 6, 8, 10]
>>> 
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> 
>>> p.map(Test.plus, [t]*4, x, y)
[4, 6, 8, 10]
>>> 
>>> res = p.amap(t.plus, x, y)
>>> res.get()
[4, 6, 8, 10]

Ответ 4

В настоящее время нет решения вашей проблемы, насколько мне известно: функция, которую вы предоставляете map(), должна быть доступна через импорт вашего модуля. Вот почему работает код robert: функцию f() можно получить, импортировав следующий код:

def f(x):
    return x*x

class Calculate(object):
    def run(self):
        p = Pool()
        return p.map(f, [1,2,3])

if __name__ == '__main__':
    cl = Calculate()
    print cl.run()

Я фактически добавил раздел "main", потому что это следует рекомендациям для платформы Windows ( "Убедитесь, что основной модуль может быть безопасно импортированный новым интерпретатором Python без возникновения непреднамеренных побочных эффектов" ).

Я также добавил заглавную букву перед Calculate, чтобы следовать PEP 8.:)

Ответ 5

Решение mrule корректно, но имеет ошибку: если дочерний элемент отправляет обратно большой объем данных, он может заполнять буфер буфера, блокируя дочерний элемент pipe.send(), в то время как родитель ждет, пока ребенок выйдет на pipe.join(). Решение состоит в том, чтобы прочитать дочерние данные до join() для дочернего элемента. Кроме того, ребенок должен закрыть родительский конец трубы, чтобы предотвратить тупик. Код ниже устанавливает это. Также имейте в виду, что этот parmap создает один процесс для каждого элемента в X. Более продвинутое решение состоит в том, чтобы использовать multiprocessing.cpu_count() для разделения X на несколько кусков, а затем объединить результаты перед возвратом. Я оставляю это как упражнение для читателя, чтобы не испортить краткость приятного ответа мруля.;)

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(ppipe, cpipe,x):
        ppipe.close()
        cpipe.send(f(x))
        cpipe.close()
    return fun

def parmap(f,X):
    pipe=[Pipe() for x in X]
    proc=[Process(target=spawn(f),args=(p,c,x)) for x,(p,c) in izip(X,pipe)]
    [p.start() for p in proc]
    ret = [p.recv() for (p,c) in pipe]
    [p.join() for p in proc]
    return ret

if __name__ == '__main__':
    print parmap(lambda x:x**x,range(1,5))

Ответ 6

Я также боролся с этим. У меня были функции как члены данных класса, в упрощенном примере:

from multiprocessing import Pool
import itertools
pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # Needed to do something like this (the following line won't work)
        return pool.map(self.f,list1,list2)  

Мне нужно было использовать функцию self.f в вызове Pool.map() из одного класса, а self.f не принимал кортеж в качестве аргумента. Поскольку эта функция была встроена в класс, мне было непонятно, как писать тип обертки.

Я решил эту проблему, используя другую оболочку, которая берет кортеж/список, где первым элементом является функция, а остальные элементы являются аргументами этой функции, называемой eval_func_tuple (f_args). Используя эту проблему, проблемную строку можно заменить return pool.map(eval_func_tuple, itertools.izip(itertools.repeat(self.f), list1, list2)). Вот полный код:

Файл: util.py

def add(a, b): return a+b

def eval_func_tuple(f_args):
    """Takes a tuple of a function and args, evaluates and returns result"""
    return f_args[0](*f_args[1:])  

Файл: main.py

from multiprocessing import Pool
import itertools
import util  

pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # The following line will now work
        return pool.map(util.eval_func_tuple, 
            itertools.izip(itertools.repeat(self.f), list1, list2)) 

if __name__ == '__main__':
    myExample = Example(util.add)
    list1 = [1, 2, 3]
    list2 = [10, 20, 30]
    print myExample.add_lists(list1, list2)  

Запуск main.py даст [11, 22, 33]. Не стесняйтесь улучшать это, например, eval_func_tuple также может быть изменен, чтобы принимать аргументы ключевых слов.

В другой заметке, в других ответах, функция "parmap" может быть более эффективной для случая большего количества Процессов, чем количество доступных ЦП. Я копирую отредактированную версию ниже. Это мой первый пост, и я не был уверен, должен ли я непосредственно редактировать исходный ответ. Я также переименовал некоторые переменные.

from multiprocessing import Process, Pipe  
from itertools import izip  

def spawn(f):  
    def fun(pipe,x):  
        pipe.send(f(x))  
        pipe.close()  
    return fun  

def parmap(f,X):  
    pipe=[Pipe() for x in X]  
    processes=[Process(target=spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]  
    numProcesses = len(processes)  
    processNum = 0  
    outputList = []  
    while processNum < numProcesses:  
        endProcessNum = min(processNum+multiprocessing.cpu_count(), numProcesses)  
        for proc in processes[processNum:endProcessNum]:  
            proc.start()  
        for proc in processes[processNum:endProcessNum]:  
            proc.join()  
        for proc,c in pipe[processNum:endProcessNum]:  
            outputList.append(proc.recv())  
        processNum = endProcessNum  
    return outputList    

if __name__ == '__main__':  
    print parmap(lambda x:x**x,range(1,5))         

Ответ 7

Функции, определенные в классах (даже внутри функций внутри классов), действительно не разборчивы. Однако это работает:

def f(x):
    return x*x

class calculate(object):
    def run(self):
        p = Pool()
    return p.map(f, [1,2,3])

cl = calculate()
print cl.run()

Ответ 8

Я взял ответы klaus se и aganders3 и сделал документированный модуль, который более читабельен и хранится в одном файле. Вы можете просто добавить его в свой проект. У этого даже есть дополнительный индикатор выполнения!

"""
The ``processes`` module provides some convenience functions
for using parallel processes in python.

Adapted from http://stackoverflow.com/a/16071616/287297

Example usage:

    print prll_map(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8], 32, verbose=True)

Comments:

"It spawns a predefined amount of workers and only iterates through the input list
 if there exists an idle worker. I also enabled the "daemon" mode for the workers so
 that KeyboardInterupt works as expected."

Pitfalls: all the stdouts are sent back to the parent stdout, intertwined.

Alternatively, use this fork of multiprocessing: 
https://github.com/uqfoundation/multiprocess
"""

# Modules #
import multiprocessing
from tqdm import tqdm

################################################################################
def apply_function(func_to_apply, queue_in, queue_out):
    while not queue_in.empty():
        num, obj = queue_in.get()
        queue_out.put((num, func_to_apply(obj)))

################################################################################
def prll_map(func_to_apply, items, cpus=None, verbose=False):
    # Number of processes to use #
    if cpus is None: cpus = min(multiprocessing.cpu_count(), 32)
    # Create queues #
    q_in  = multiprocessing.Queue()
    q_out = multiprocessing.Queue()
    # Process list #
    new_proc  = lambda t,a: multiprocessing.Process(target=t, args=a)
    processes = [new_proc(apply_function, (func_to_apply, q_in, q_out)) for x in range(cpus)]
    # Put all the items (objects) in the queue #
    sent = [q_in.put((i, x)) for i, x in enumerate(items)]
    # Start them all #
    for proc in processes:
        proc.daemon = True
        proc.start()
    # Display progress bar or not #
    if verbose:
        results = [q_out.get() for x in tqdm(range(len(sent)))]
    else:
        results = [q_out.get() for x in range(len(sent))]
    # Wait for them to finish #
    for proc in processes: proc.join()
    # Return results #
    return [x for i, x in sorted(results)]

################################################################################
def test():
    def slow_square(x):
        import time
        time.sleep(2)
        return x**2
    objs    = range(20)
    squares = prll_map(slow_square, objs, 4, verbose=True)
    print "Result: %s" % squares

EDIT: добавлено предложение @alexander-mcfarlane и тестовая функция

Ответ 9

Я знаю, что это было задано более 6 лет назад, но просто захотелось добавить мое решение, поскольку некоторые из приведенных выше предложений кажутся ужасно сложными, но мое решение было на самом деле очень простым.

Все, что мне нужно было сделать, - это обернуть вызов pool.map() вспомогательной функции. Передача объекта класса вместе с аргументами для метода как кортежа, который выглядел примерно так.

def run_in_parallel(args):
    return args[0].method(args[1])

myclass = MyClass()
method_args = [1,2,3,4,5,6]
args_map = [ (myclass, arg) for arg in method_args ]
pool = Pool()
pool.map(run_in_parallel, args_map)

Ответ 10

Я изменил метод klaus se, потому что, пока он работал у меня с небольшими списками, он зависал, когда количество элементов было ~ 1000 или больше. Вместо того, чтобы нажимать задания по одному с условием остановки None, я загружаю очередь ввода все сразу и просто позволяю процессам нажимать на нее до тех пор, пока она не будет пуста.

from multiprocessing import cpu_count, Queue, Process

def apply_func(f, q_in, q_out):
    while not q_in.empty():
        i, x = q_in.get()
        q_out.put((i, f(x)))

# map a function using a pool of processes
def parmap(f, X, nprocs = cpu_count()):
    q_in, q_out   = Queue(), Queue()
    proc = [Process(target=apply_func, args=(f, q_in, q_out)) for _ in range(nprocs)]
    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [p.start() for p in proc]
    res = [q_out.get() for _ in sent]
    [p.join() for p in proc]

    return [x for i,x in sorted(res)]

Редактирование: к сожалению, теперь я использую эту ошибку в своей системе: Максимальное ограничение для максимальной длины очереди 32767, надеюсь, что обходные пути там помогут.

Ответ 11

Я знаю, что этот вопрос задавался 8 лет и 10 месяцев назад, но я хочу представить вам свое решение:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @staticmethod
    def methodForMultiprocessing(x):
        print(x*x)

    def main(self):
        if __name__ == "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

Вам просто нужно превратить функцию класса в статическую функцию. Но это также возможно с помощью метода класса:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @classmethod
    def methodForMultiprocessing(cls, x):
        print(x*x)

    def main(self):
        if __name__ == "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

Протестировано в Python 3.7.3

Ответ 12

Я не уверен, был ли этот подход принят, но работа, которую я использую, это:

from multiprocessing import Pool

t = None

def run(n):
    return t.f(n)

class Test(object):
    def __init__(self, number):
        self.number = number

    def f(self, x):
        print x * self.number

    def pool(self):
        pool = Pool(2)
        pool.map(run, range(10))

if __name__ == '__main__':
    t = Test(9)
    t.pool()
    pool = Pool(2)
    pool.map(run, range(10))

Вывод должен быть:

0
9
18
27
36
45
54
63
72
81
0
9
18
27
36
45
54
63
72
81

Ответ 13

class Calculate(object):
  # Your instance method to be executed
  def f(self, x, y):
    return x*y

if __name__ == '__main__':
  inp_list = [1,2,3]
  y = 2
  cal_obj = Calculate()
  pool = Pool(2)
  results = pool.map(lambda x: cal_obj.f(x, y), inp_list)

Существует вероятность, что вы захотите применить эту функцию для каждого другого экземпляра класса. Тогда это решение для этого также

class Calculate(object):
  # Your instance method to be executed
  def __init__(self, x):
    self.x = x

  def f(self, y):
    return self.x*y

if __name__ == '__main__':
  inp_list = [Calculate(i) for i in range(3)]
  y = 2
  pool = Pool(2)
  results = pool.map(lambda x: x.f(y), inp_list)

Ответ 14

Вот мое решение, которое я считаю немного менее хакерским, чем большинство других здесь. Это похоже на ночной ответ.

someclasses = [MyClass(), MyClass(), MyClass()]

def method_caller(some_object, some_method='the method'):
    return getattr(some_object, some_method)()

othermethod = partial(method_caller, some_method='othermethod')

with Pool(6) as pool:
    result = pool.map(othermethod, someclasses)

Ответ 15

С http://www.rueckstiess.net/research/snippets/show/ca1d7d90 и http://qingkaikong.blogspot.com/2016/12/python-parallel-method-in-class.html

Мы можем создать внешнюю функцию и заполнить ее объектом класса self:

from joblib import Parallel, delayed
def unwrap_self(arg, **kwarg):
    return square_class.square_int(*arg, **kwarg)

class square_class:
    def square_int(self, i):
        return i * i

    def run(self, num):
        results = []
        results = Parallel(n_jobs= -1, backend="threading")\
            (delayed(unwrap_self)(i) for i in zip([self]*len(num), num))
        print(results)

ИЛИ без JobLib:

from multiprocessing import Pool
import time

def unwrap_self_f(arg, **kwarg):
    return C.f(*arg, **kwarg)

class C:
    def f(self, name):
        print 'hello %s,'%name
        time.sleep(5)
        print 'nice to meet you.'

    def run(self):
        pool = Pool(processes=2)
        names = ('frank', 'justin', 'osi', 'thomas')
        pool.map(unwrap_self_f, zip([self]*len(names), names))

if __name__ == '__main__':
    c = C()
    c.run()

Ответ 16

Вы можете запустить свой код без каких-либо проблем, если вы каким-то образом вручную проигнорируете объект Pool из списка объектов в классе, потому что он не является pickle как говорится в ошибке. Вы можете сделать это с __getstate__ функции __getstate__ (смотрите здесь) следующим образом. Объект Pool попытается найти функции __getstate__ и __setstate__ и выполнить их, если найдет их при запуске map, map_async т.д.

class calculate(object):
    def __init__(self):
        self.p = Pool()
    def __getstate__(self):
        self_dict = self.__dict__.copy()
        del self_dict['p']
        return self_dict
    def __setstate__(self, state):
        self.__dict__.update(state)

    def f(self, x):
        return x*x
    def run(self):
        return self.p.map(self.f, [1,2,3])

Затем сделайте:

cl = calculate()
cl.run()

даст вам вывод:

[1, 4, 9]

Я протестировал приведенный выше код в Python 3.x, и он работает.