Istnieje inny skrót, którego możesz użyć, chociaż może być nieefektywny w zależności od tego, co znajduje się w twojej klasie.
Jak wszyscy powiedzieli, problem polega na tym, że multiprocessing
kod musi zalewać rzeczy, które wysyła do podprocesów, które uruchomił, a moduł wybierający nie stosuje metod instancji.
Jednak zamiast wysyłać metodę instancji, możesz wysłać rzeczywistą instancję klasy oraz nazwę funkcji do wywołania, do zwykłej funkcji, która następnie używa getattr
do wywołania metody instancji, tworząc w ten sposób powiązaną metodę w Pool
podprocesie. Jest to podobne do definiowania __call__
metody, z tą różnicą, że można wywołać więcej niż jedną funkcję składową.
Ukradłem kod @ EricH. Z jego odpowiedzi i trochę go opatrzyłem komentarzem (przepisałem go stąd wszystkie zmiany nazwy i takie, z jakiegoś powodu wydawało się to łatwiejsze niż wycinanie i wklejanie :-)) dla zilustrowania całej magii:
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)
Dane wyjściowe pokazują, że rzeczywiście, konstruktor jest wywoływany raz (w oryginalnym pid), a destruktor jest wywoływany 9 razy (raz dla każdej wykonanej kopii = 2 lub 3 razy na proces-pulę-proces, zależnie od potrzeby, plus raz w oryginale proces). Jest to często OK, tak jak w tym przypadku, ponieważ domyślny moduł wybierający tworzy kopię całej instancji i (częściowo) potajemnie wypełnia ją ponownie - w tym przypadku wykonując:
obj = object.__new__(Klass)
obj.__dict__.update({'count':1})
- dlatego mimo że destruktor jest wywoływany osiem razy w trzech procesach roboczych, odlicza od 1 do 0 za każdym razem - ale oczywiście nadal możesz mieć kłopoty w ten sposób. W razie potrzeby możesz podać własne __setstate__
:
def __setstate__(self, adict):
self.count = adict['count']
na przykład w tym przypadku.
PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed