Há outro atalho que você pode usar, embora possa ser ineficiente, dependendo do que está em suas instâncias de classe.
Como todos disseram, o problema é que o multiprocessing
código precisa selecionar as coisas que envia para os subprocessos que foram iniciados, e o selecionador não usa métodos de instância.
No entanto, em vez de enviar o método da instância, você pode enviar a instância da classe real, mais o nome da função a ser chamada, para uma função comum que então usa getattr
para chamar o método da instância, criando o método vinculado no Pool
subprocesso. Isso é semelhante à definição de um __call__
método, exceto que você pode chamar mais de uma função de membro.
Roubar o código do @ EricH. Da sua resposta e anotá-lo um pouco (redigitei-o, portanto, todo o nome muda e tal, por algum motivo isso parecia mais fácil do que copiar e colar :-)) para ilustrar toda a mágica:
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)
A saída mostra que, de fato, o construtor é chamado uma vez (no pid original) e o destruidor é chamado 9 vezes (uma vez para cada cópia feita = 2 ou 3 vezes por processo de trabalho conjunto, conforme necessário, mais uma vez no original processo). Isso geralmente é bom, como neste caso, já que o seletor padrão faz uma cópia de toda a instância e (semi-) a preenche secretamente - nesse caso, fazendo:
obj = object.__new__(Klass)
obj.__dict__.update({'count':1})
- é por isso que, embora o destruidor seja chamado oito vezes nos três processos de trabalho, ele é reduzido de 1 a 0 a cada vez - mas é claro que você ainda pode ter problemas dessa maneira. Se necessário, você pode fornecer o seu próprio __setstate__
:
def __setstate__(self, adict):
self.count = adict['count']
neste caso, por exemplo.
PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed