Çok işlemli Pool.map () kullanılırken <tür 'instancemethod'> turşusu alınamıyor


218

Ben kullanmaya çalışıyorum multiprocessing'ın Pool.map()aynı anda işi bölmek için işlevi. Aşağıdaki kodu kullandığımda, iyi çalışıyor:

import multiprocessing

def f(x):
    return x*x

def go():
    pool = multiprocessing.Pool(processes=4)        
    print pool.map(f, range(10))


if __name__== '__main__' :
    go()

Ancak, onu daha nesne yönelimli bir yaklaşımla kullandığımda işe yaramıyor. Verdiği hata mesajı:

PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup
__builtin__.instancemethod failed

Bu benim ana program aşağıdaki olduğunda oluşur:

import someClass

if __name__== '__main__' :
    sc = someClass.someClass()
    sc.go()

ve benim someClasssınıfım:

import multiprocessing

class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(self.f, range(10))

Sorunun ne olabileceğini bilen var mı, yoksa bunun kolay bir yolu var mı?


4
f iç içe geçmiş bir PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
işlevse

Yanıtlar:


122

Sorun şudur ki, çoklu işlem, bunları süreçler arasında saplamak için bir şeyler seçmelidir ve bağlı yöntemler seçilemez. Geçici çözüm ("kolay" olup olmadığına bakılmaksızın ;-), bu yöntemlerin alınmasını sağlamak için programınıza altyapıyı eklemektir ve copy_reg standart kitaplık yöntemiyle kaydedilmesidir.

Örneğin, Steven Bethard'ın bu iş parçacığına ( iş parçacığının sonuna doğru) katkısı, yöntem dekapaj / açma işlemine izin vermek için mükemmel bir şekilde uygulanabilir bir yaklaşım göstermektedir copy_reg.


Bu harika, teşekkürler. Her nasılsa bir şekilde ilerlemiş gibi görünüyor: pastebin.ca/1693348 kodunu kullanarak Şimdi bir RuntimeError alıyorum: maksimum özyineleme derinliği aşıldı. Etrafıma baktım ve bir forum gönderisi maksimum derinliği 1500'e (varsayılan 1000'den) artırmayı önerdi ama orada hiç neşe duymadım. Dürüst olmak gerekirse, bazı nedenlerden dolayı kodun bir döngüde dekapaj ve unpickling olmadığı sürece, (en azından benim kodumun) hangi kısmın kontrolden çıktığını göremiyorum Steven'ın kodu OO'd mu?
ventolin

1
Sizin _pickle_methoddöner self._unpickle_method, bir bağımlı yöntem; Tabii ki turşu şimdi BU'yu turşu yapmaya çalışıyor - ve bunu söylediğin gibi yapıyor: arayarak _pickle_method, özyineli olarak. Yani OOkodu bu şekilde girerek, kaçınılmaz olarak sonsuz özyineleme eklediniz. Steven'ın koduna geri dönmeyi öneririm (ve uygun olmadığında OO sunağına ibadet etmemek: Python'daki birçok şey en iyi şekilde daha işlevsel bir şekilde yapılır ve bu birdir).
Alex Martelli


15
Süper süper tembel için , gerçek karıştırılmamış kodu göndermek için rahatsız tek cevap bakın ...
Cerin

2
Turşu problemini düzeltmenin / atlatmanın başka bir yolu dereotu kullanmaktır, cevabımı görmek için stackoverflow.com/questions/8804830/…
rocksportrocker

74

Tüm bu çözümler çirkindir, çünkü standart kütüphane dışına çıkmadıkça çoklu işleme ve dekapaj kırılmış ve sınırlıdır.

Bir multiprocessingçağrı çatalı pathos.multiprocesssingkullanıyorsanız, çoklu işlem mapişlevlerinde sınıfları ve sınıf yöntemlerini doğrudan kullanabilirsiniz . Bunun nedeni dill, pickleveya yerine kullanılır cPickleve dillpython'daki hemen hemen her şeyi serileştirebilir.

pathos.multiprocessingAyrıca uyumsuz bir harita işlevi sağlar ... ve can map(örn birden argümanlarla fonksiyonlar map(math.pow, [1,2,3], [4,5,6]))

Bakınız: Çoklu işleme ve dereotu birlikte ne yapabilir?

ve: http://matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization/

>>> import pathos.pools as pp
>>> p = pp.ProcessPool(4)
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> x = [0,1,2,3]
>>> y = [4,5,6,7]
>>> 
>>> p.map(add, x, y)
[4, 6, 8, 10]
>>> 
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> 
>>> p.map(Test.plus, [t]*4, x, y)
[4, 6, 8, 10]
>>> 
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]

Ve sadece açık olmak gerekirse, ilk etapta yapmak istediğinizi tam olarak isteyebilirsiniz ve isterseniz yorumlayıcıdan yapabilirsiniz.

>>> import pathos.pools as pp
>>> class someClass(object):
...   def __init__(self):
...     pass
...   def f(self, x):
...     return x*x
...   def go(self):
...     pool = pp.ProcessPool(4)
...     print pool.map(self.f, range(10))
... 
>>> sc = someClass()
>>> sc.go()
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> 

Kodu buradan alın: https://github.com/uqfoundation/pathos


3
Bu yanıtı lütfen patos.pp'ye göre güncelleyebilir misiniz, çünkü patos.multiprocessing artık mevcut değil mi?
Saheel Godhane

10
Ben pathosyazarım. Bahsettiğiniz sürüm birkaç yaşında. Github üzerinde sürümü deneyin, kullanabilirsiniz pathos.ppveya github.com/uqfoundation/ppft .
Mike McKerns

1
veya github.com/uqfoundation/pathos . @SaheelGodhane: Yeni bir sürüm gecikmiş ancak kısa süre içinde yayınlanacak.
Mike McKerns

3
Önce pip install setuptools, sonra pip install git+https://github.com/uqfoundation/pathos.git@master. Bu uygun bağımlılıkları alacaktır. Yeni bir sürüm neredeyse hazır… şimdi neredeyse her şey pathospencerelerde de çalışıyor ve 3.xuyumlu.
Mike McKerns

1
@Rika: Evet. engelleme, yinelemeli ve zaman uyumsuz haritalar kullanılabilir.
Mike McKerns

35

Ayrıca , içinde çağıran ve daha sonra havuza bir örneğini ileten bir __call__()yöntem tanımlayabilirsiniz . Bu nesne seçilebilir ve iyi çalışıyor (benim için) ...someClass()someClass.go()someClass()


3
Bu, Alex Martelli tarafından önerilen teknikten çok daha kolay, ancak çok işlem havuzunuza sınıf başına sadece bir yöntem göndermekle sınırlısınız.
kullanımdan kaldırıldı

6
Akılda ayıya Bir başka ayrıntı da olmasıdır sadece , sınıf kendisi değil turşusu olur o nesnenin (sınıf örneği). Bu nedenle, herhangi bir sınıf özniteliğini varsayılan değerlerinden değiştirdiyseniz, bu değişiklikler farklı işlemlere yayılmaz. Çözüm, işlevinizin gereksinim duyduğu her şeyin bir örnek özelliği olarak depolandığından emin olmaktır.
kullanımdan kaldırıldı

2
@dorvak ile basit bir örnek gösterebilir misiniz __call__()? Sanırım cevabınız daha temiz olabilir - bu hatayı anlamakta zorlanıyorum ve ilk kez aramayı görmeye geldim. Bu arada, bu cevap da çok işlemciliğin ne olduğunu açıklığa kavuşturmaya yardımcı oluyor: [ stackoverflow.com/a/20789937/305883]
user305883

1
Buna bir örnek verebilir misiniz?
frmsaul

1
Bunun için örnek kodla birlikte (şu anda bu sorunun altında) yeni bir yanıt gönderildi.
Aaron

22

Steven Bethard'ın çözümü için bazı kısıtlamalar olsa da:

Sınıf yönteminizi bir işlev olarak kaydettiğinizde, yönteminizin işlenmesi her tamamlandığında sınıfınızın yıkıcısı şaşırtıcı bir şekilde çağrılır. Bu nedenle, sınıfınızın yöntemini n kez çağıran 1 örneğiniz varsa, üyeler 2 çalıştırma arasında kaybolabilir ve bir ileti malloc: *** error for object 0x...: pointer being freed was not allocated(örn. Açık üye dosyası) veya pure virtual method called, terminate called without an active exception(kullandığım bir üye nesnenin kullanım ömründen daha kısa olduğu anlamına gelir ) ne düşündüğümü). Havuz boyutu daha büyük n ile uğraşırken bu var. Kısa bir örnek:

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

# --------- see Stenven's solution above -------------
from copy_reg import pickle
from types import MethodType

def _pickle_method(method):
    func_name = method.im_func.__name__
    obj = method.im_self
    cls = method.im_class
    return _unpickle_method, (func_name, obj, cls)

def _unpickle_method(func_name, obj, cls):
    for cls in cls.mro():
        try:
            func = cls.__dict__[func_name]
        except KeyError:
            pass
        else:
            break
    return func.__get__(obj, cls)


class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multi-processing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self.process_obj, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __del__(self):
        print "... Destructor"

    def process_obj(self, index):
        print "object %d" % index
        return "results"

pickle(MethodType, _pickle_method, _unpickle_method)
Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once)

Çıktı:

Constructor ...
object 0
object 1
object 2
... Destructor
object 3
... Destructor
object 4
... Destructor
object 5
... Destructor
object 6
... Destructor
object 7
... Destructor
... Destructor
... Destructor
['results', 'results', 'results', 'results', 'results', 'results', 'results', 'results']
... Destructor

__call__[Yok, ...] sonuçlarından okunan çünkü yöntem, bu nedenle eşdeğer değildir:

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multiprocessing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __call__(self, i):
        self.process_obj(i)

    def __del__(self):
        print "... Destructor"

    def process_obj(self, i):
        print "obj %d" % i
        return "result"

Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once), 
# **and** results are empty !

Yani her iki yöntemin hiçbiri tatmin edici değil ...


7
Sen almak Nonetanımın çünkü geri __call__eksik returnolması gerektiği: return self.process_obj(i).
torek

1
@Eric Aynı hatayı alıyordum ve bu çözümü denedim, ancak "cPickle.PicklingError: <type 'function'>: öznitelik arama yerleşik .function başarısız oldu" olarak yeni bir hata almaya başladım . Arkasında olası bir nedenin ne olabileceğini biliyor musunuz?
Naman

15

Kullanabileceğiniz başka bir kısayol daha var, ancak sınıf örneklerinizde ne olduğuna bağlı olarak verimsiz olabilir.

Herkesin dediği gibi sorun şu ki multiprocessing kodun başlattığı alt süreçlere gönderdiği şeyleri seçmesi ve toplayıcının örnek yöntemleri yapmamasıdır.

Ancak, example-yöntemini göndermek yerine, gerçek sınıf örneğini ve çağrılacak işlevin adını, daha sonra getattrexample-yöntemini çağırmak için kullanılan sıradan bir işleve , böylece Poolalt işlemde ilişkili yöntemi oluşturabilirsiniz . Bu, __call__birden fazla üye işlevini çağırabilmeniz dışında bir yöntem tanımlamaya benzer .

@ EricH.'nin cevabından kodunu çalmak ve biraz açıklama eklemek (tüm isim değişikliklerini ve bu nedenle, bazı sebeplerden dolayı bu kesip yapıştırmaktan daha kolay görünüyordu :-)) tüm büyüyü göstermek için:

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)

Çıktı, gerçekten de yapıcıya bir kez (orijinal pid'de) çağrıldığını ve yıkıcıya 9 kez çağrıldığını (yapılan her kopya için bir kez = havuz çalışanı işlemi başına gerektiği gibi 2 veya 3 kez, ayrıca orijinalinde bir kez) işlemi). Varsayılan yakalayıcı tüm örneğin bir kopyasını oluşturduğundan ve (yarı) gizlice yeniden doldurduğu için, bu durumda olduğu gibi, genellikle sorun olmaz:

obj = object.__new__(Klass)
obj.__dict__.update({'count':1})

- bu yüzden yıkıcı üç işçi işleminde sekiz kez çağrılsa da, her seferinde 1'den 0'a kadar geri sayar - ama elbette yine de bu şekilde sorun yaşayabilirsiniz. Gerekirse, kendinizinkini sağlayabilirsiniz __setstate__:

    def __setstate__(self, adict):
        self.count = adict['count']

bu durumda örneğin.


1
Bu sorun, bu sorunun en iyi yanıtıdır, çünkü turşu olmayan varsayılan davranışa uygulamak en kolayıdır
Matt Taylor

12

Ayrıca , içinde çağıran ve daha sonra havuza bir örneğini ileten bir __call__()yöntem tanımlayabilirsiniz . Bu nesne seçilebilir ve iyi çalışıyor (benim için) ...someClass()someClass.go()someClass()

class someClass(object):
   def __init__(self):
       pass
   def f(self, x):
       return x*x

   def go(self):
      p = Pool(4)
      sc = p.map(self, range(4))
      print sc

   def __call__(self, x):   
     return self.f(x)

sc = someClass()
sc.go()

3

Yukarıdaki parisjohn'un çözümü benimle çalışıyor. Ayrıca kod temiz ve anlaşılması kolay görünüyor. Benim durumumda Havuz kullanarak aramak için birkaç işlevi vardır, bu yüzden parisjohn kodunu biraz aşağıda değiştirdim. Ben made çağrı çeşitli işlevleri çağırmak için muktedir ve fonksiyon adları gelen argüman dict geçirilir go():

from multiprocessing import Pool
class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def g(self, x):
        return x*x+1    

    def go(self):
        p = Pool(4)
        sc = p.map(self, [{"func": "f", "v": 1}, {"func": "g", "v": 2}])
        print sc

    def __call__(self, x):
        if x["func"]=="f":
            return self.f(x["v"])
        if x["func"]=="g":
            return self.g(x["v"])        

sc = someClass()
sc.go()

1

Bunun potansiyel olarak önemsiz bir çözümü, multiprocessing.dummy . Bu, Python 2.7'de bu soruna sahip görünmeyen çok işlemli arabirimin iş parçacığı tabanlı bir uygulamasıdır. Burada çok fazla deneyimim yok, ancak bu hızlı içe aktarma değişikliği, bir sınıf yönteminde Apply_async çağırmamı sağladı.

Birkaç iyi kaynak multiprocessing.dummy:

https://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.dummy

http://chriskiehl.com/article/parallelism-in-one-line/


1

someClass.fSınıftan herhangi bir veri devralmayan ve sınıfa hiçbir şey bağlamayan bu basit durumda, ayırmak için olası bir çözüm olacaktır f:

import multiprocessing


def f(x):
    return x*x


class someClass(object):
    def __init__(self):
        pass

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(f, range(10))

1

Neden ayrı fonk kullanmıyorsunuz?

def func(*args, **kwargs):
    return inst.method(args, kwargs)

print pool.map(func, arr)

1

Ben aynı sorunu koştu ama bu nesneleri işlemler arasında taşımak için kullanılabilecek bir JSON kodlayıcı olduğunu öğrendim.

from pyVmomi.VmomiSupport import VmomiJSONEncoder

Listenizi oluşturmak için bunu kullanın:

jsonSerialized = json.dumps(pfVmomiObj, cls=VmomiJSONEncoder)

Ardından eşlenen işlevde, nesneyi kurtarmak için bunu kullanın:

pfVmomiObj = json.loads(jsonSerialized)

0

Güncelleme: Bu yazının yapıldığı günden itibaren adlandırılmış gruplar seçilebilir (python 2.7 ile başlayarak)

Buradaki sorun, alt süreçlerin nesnenin sınıfını alamamasıdır - bu durumda P sınıfı - çok modelli bir proje söz konusu olduğunda, P Sınıfı, alt sürecin kullanıldığı her yerde içe aktarılabilir olmalıdır

hızlı bir çözüm, onu globallere () (

globals()["P"] = P
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.