Многопроцессорность: как использовать 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, и он работает.