C'è un'altra scorciatoia che puoi usare, anche se può essere inefficiente a seconda di cosa c'è nelle tue istanze di classe.
Come tutti hanno detto, il problema è che il multiprocessing
codice deve decapare le cose che invia ai sottoprocessi che ha avviato e il pickler non esegue metodi di istanza.
Tuttavia, invece di inviare il metodo di istanza, è possibile inviare l'istanza di classe effettiva, oltre al nome della funzione da chiamare, a una funzione ordinaria che quindi utilizza getattr
per chiamare il metodo di istanza, creando così il metodo associato nel Pool
sottoprocesso. Ciò è simile alla definizione di un __call__
metodo, tranne per il fatto che è possibile chiamare più di una funzione membro.
Rubare il codice di @ EricH. dalla sua risposta e annotarlo un po '(l'ho scritto di nuovo, quindi tutti i nomi cambiano e così, per qualche motivo questo sembrava più facile del taglia e incolla :-)) per l'illustrazione di tutta la magia:
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)
L'output mostra che, in effetti, il costruttore viene chiamato una volta (nel pid originale) e il distruttore viene chiamato 9 volte (una volta per ogni copia effettuata = 2 o 3 volte per processo di pool-worker secondo necessità, più una volta nell'originale processi). Questo è spesso OK, come in questo caso, poiché il pickler predefinito crea una copia dell'intera istanza e (semi) ripopola segretamente, in questo caso, facendo:
obj = object.__new__(Klass)
obj.__dict__.update({'count':1})
— Ecco perché anche se il distruttore viene chiamato otto volte nei tre processi di lavoro, conta da 1 a 0 ogni volta — ma ovviamente puoi ancora metterti nei guai in questo modo. Se necessario, puoi fornire il tuo __setstate__
:
def __setstate__(self, adict):
self.count = adict['count']
in questo caso ad esempio.
PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed