Есть еще один ярлык, который вы можете использовать, хотя он может быть неэффективным в зависимости от того, что находится в вашем классе.
Как все говорили, проблема в том, что multiprocessing
код должен перебирать вещи, которые он отправляет запущенным подпроцессам, а средство выбора не выполняет методы экземпляра.
Однако вместо отправки метода экземпляра вы можете отправить фактический экземпляр класса плюс имя вызываемой функции в обычную функцию, которая затем используется getattr
для вызова метода экземпляра, создавая связанный метод в Pool
подпроцессе. Это похоже на определение __call__
метода за исключением того, что вы можете вызывать более одной функции-члена.
Кража кода @ EricH. Из его ответа и его комментирование (я набрал его заново, поэтому все имена меняются и тому подобное, по какой-то причине это казалось проще, чем вырезать и вставить :-)) для иллюстрации всей магии:
import multiprocessing
import os
def call_it(instance, name, args=(), kwargs=None):
"indirect caller for instance methods and multiprocessing"
if kwargs is None:
kwargs = {}
return getattr(instance, name)(*args, **kwargs)
class Klass(object):
def __init__(self, nobj, workers=multiprocessing.cpu_count()):
print "Constructor (in pid=%d)..." % os.getpid()
self.count = 1
pool = multiprocessing.Pool(processes = workers)
async_results = [pool.apply_async(call_it,
args = (self, 'process_obj', (i,))) for i in range(nobj)]
pool.close()
map(multiprocessing.pool.ApplyResult.wait, async_results)
lst_results = [r.get() for r in async_results]
print lst_results
def __del__(self):
self.count -= 1
print "... Destructor (in pid=%d) count=%d" % (os.getpid(), self.count)
def process_obj(self, index):
print "object %d" % index
return "results"
Klass(nobj=8, workers=3)
Выходные данные показывают, что действительно, конструктор вызывается один раз (в исходном pid), а деструктор вызывается 9 раз (один раз для каждой сделанной копии = 2 или 3 раза за пул-рабочий процесс, в зависимости от необходимости, плюс один раз в оригинале обработать). Это часто нормально, как в этом случае, так как сборщик по умолчанию делает копию всего экземпляра и (частично) тайно повторно заполняет его - в этом случае делает:
obj = object.__new__(Klass)
obj.__dict__.update({'count':1})
Вот почему, несмотря на то, что деструктор вызывается восемь раз в трех рабочих процессах, он ведет отсчет от 1 до 0 каждый раз, но, конечно, вы все равно можете столкнуться с неприятностями. При необходимости вы можете предоставить свой собственный__setstate__
:
def __setstate__(self, adict):
self.count = adict['count']
в этом случае, например.
PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed