Home > Software > multiprocessing.Pool i “Can’t pickle <type ‘instancemethod’>”

multiprocessing.Pool i “Can’t pickle <type ‘instancemethod’>”

August 14th, 2009 Dariusz Suchojad

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.

Share
Categories: Software Tags:
Comments are closed.