multiprocessing.Pool i “Can’t pickle <type ‘instancemethod’>”
Spędziłem właśnie chwilę na odkryciu tego, w jaki sposób w Pythonie, korzystając z puli procesów multiprocessing.Pool, przekazać metodę do wywołania, która jest metodą instancji, a nie po prostu funkcją w module. Korzystam z Pythona 2.5 i pakietu multiprocessing ściągniętego z PyPi w wersji 2.6.2.1.
Właściwy kod jest bardziej rozbudowany, i przede wszystkim wszystkie zależności pomiędzy klasami wstrzykiwane są przez Spring Pythona, ale uproszczona wersja dobrze ilustrująca sytuację jest poniżej. Istotne jest to, że od instancji klasy Handler nie mogę oczekiwać niczego innego ponad zdefiniowanie metody handle(self, message), nie mogę więc spodziewać się tego, że instancje te będą w jakikolwiek sposób customizowały picklowanie samych siebie.
Pierwsza próba, wyglądająca teoretycznie dobrze, wygląda tak:
from multiprocessing import Pool
class Handler(object):
def handle(self, message):
print message
class Listener(object):
def __init__(self, pool, handler, message):
self.pool = pool
self.handler = handler
self.message = message
def run(self):
self.pool.apply_async(self.handler.handle, self.message)
if __name__ == "__main__":
import time
pool = Pool(20)
message = "Hello world!"
listener = Listener(pool, Handler(), message)
listener.run()
time.sleep(3)
Jest to, niestety, wersja, której uruchomienie zakończy się pięknym wyjątkiem:
$ python cant-pickle.py Exception in thread Thread-1: Traceback (most recent call last): File "/usr/lib/python2.5/threading.py", line 486, in __bootstrap_inner self.run() File "/usr/lib/python2.5/threading.py", line 446, in run self.__target(*self.__args, **self.__kwargs) File "/usr/lib/python2.5/site-packages/multiprocessing-2.6.2.1-py2.5-linux-i686.egg/multiprocessing/pool.py", line 225, in _handle_tasks put(task) PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup __builtin__.instancemethod failed
Poprawna wersja wygląda tak:
from multiprocessing import Pool def handle(handler, message): handler.handle(message) class Handler(object): def handle(self, message): print message class Listener(object): def __init__(self, pool, handler, message): self.pool = pool self.handler = handler self.message = message def run(self): self.pool.apply_async(handle, [self.handler, self.message]) if __name__ == "__main__": import time pool = Pool(20) message = "Hello world!" listener = Listener(pool, Handler(), message) listener.run() time.sleep(3) $ python test-cantpickle.py Hello world! $
Czyli jednak nie ma lekko, trzeba opakować metodę instancji w funkcję i dopiero wtedy przekazać do przetwarzania w puli, wtedy multiprocessing.Pool działa tak jak trzeba.
Swoją drogą, jeśli dostarczymy odpowiedniej pracy do wykonania to multiprocessing bardzo ładnie wysysa wszystkie core’y, więc historie, że Python nie jest w stanie korzystać ze wszystkich procesorów ze względu na GIL można spokojnie włożyć między bajki.
