Ви можете скористатися ще однією комбінацією клавіш, хоча вона може бути неефективною залежно від примірників вашого класу.
Як усі кажуть, проблема полягає в тому, що 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)
Вихід показує, що, дійсно, конструктор викликається один раз (у оригінальному підручнику), а деструктор викликається 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