ไม่สามารถเลือก <type 'instancemethod'> เมื่อใช้มัลติโพรเซสซิง Pool.map ()


218

ฉันพยายามที่จะใช้งานmultiprocessingของPool.map()ฟังก์ชั่นที่จะแบ่งออกงานพร้อมกัน เมื่อฉันใช้รหัสต่อไปนี้มันทำงานได้ดี:

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()

อย่างไรก็ตามเมื่อฉันใช้มันในแนวทางเชิงวัตถุมากกว่ามันไม่ทำงาน ข้อความแสดงข้อผิดพลาด:

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

สิ่งนี้เกิดขึ้นเมื่อโปรแกรมหลักของฉันคือ:

import someClass

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

และต่อไปนี้เป็นsomeClassชั้นเรียนของฉัน:

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))

ใครรู้ว่าปัญหาอาจเป็นวิธีที่ง่ายหรือรอบ ๆ ?


4
ถ้า f เป็นฟังก์ชันซ้อนกันมีข้อผิดพลาดที่คล้ายกันPicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
ggg

คำตอบ:


122

ปัญหาคือมัลติโพรเซสซิงต้องดองสิ่งต่าง ๆ เพื่อใช้ในกระบวนการและวิธีการเชื่อมโยงไม่สามารถเลือกได้ วิธีแก้ปัญหา (ไม่ว่าคุณจะพิจารณาว่า "ง่าย" หรือไม่ ;-) คือการเพิ่มโครงสร้างพื้นฐานให้กับโปรแกรมของคุณเพื่อให้วิธีการดังกล่าวได้รับการลงทะเบียนโดยใช้วิธีการไลบรารีมาตรฐานcopy_reg

ตัวอย่างเช่นผลงานของสตีเว่น Bethard เพื่อหัวข้อนี้ (ในช่วงปลายของด้าย) แสดงให้เห็นถึงวิธีการหนึ่งที่สามารถทำงานได้อย่างสมบูรณ์แบบเพื่อช่วยให้วิธีการดอง / unpickling copy_regผ่าน


เยี่ยมมาก - ขอบคุณ ดูเหมือนว่าจะมีความคืบหน้าในทางใดทางหนึ่ง: การใช้รหัสที่pastebin.ca/1693348ตอนนี้ฉันได้รับ RuntimeError: ความลึกการเรียกซ้ำสูงสุดเกิน ฉันมองไปรอบ ๆ และหนึ่งโพสต์ฟอรัมแนะนำให้เพิ่มความลึกสูงสุดถึง 1500 (จากค่าเริ่มต้น 1,000) แต่ฉันไม่มีความสุข พูดตามตรงฉันไม่เห็นว่าส่วนใด (ของรหัสของฉันอย่างน้อย) สามารถเรียกซ้ำนอกเหนือการควบคุมได้เว้นแต่ว่าด้วยเหตุผลบางอย่างรหัสนั้นมีการดองและไม่แก้ในวงเนื่องจากมีการเปลี่ยนแปลงเล็กน้อยที่ฉันทำ โค้ดของ Steven
ventolin

1
_pickle_methodผลตอบแทนของคุณself._unpickle_methodวิธีการที่ถูกผูกไว้; ดังนั้นแน่นอนดองตอนนี้พยายามที่จะดองที่ - และมันจะเป็นไปตามที่คุณได้บอกไป: โดยการโทร_pickle_methodซ้ำ เช่นโดยOOการใช้รหัสในวิธีนี้คุณได้แนะนำการเรียกซ้ำแบบไม่สิ้นสุดอย่างหลีกเลี่ยงไม่ได้ ฉันขอแนะนำให้กลับไปที่รหัสของสตีเวน (และไม่บูชาที่แท่นบูชาของ OO เมื่อไม่เหมาะสม: หลาย ๆ อย่างใน Python ทำได้ดีที่สุดในการใช้งานได้มากกว่าและนี่คือสิ่งเดียว)
Alex Martelli


15
สำหรับซูเปอร์ขี้เกียจสุดโปรดดูคำตอบเดียวที่ใส่ใจที่จะโพสต์รหัสไม่ใช่แหลกเหลวที่เกิดขึ้นจริง ...
Cerin

2
อีกวิธีหนึ่งในการแก้ไข / หลีกเลี่ยงปัญหาการดองคือการใช้ผักชีฝรั่งดูคำตอบของฉันstackoverflow.com/questions/8804830//
rockportrocker

74

โซลูชันทั้งหมดเหล่านี้น่าเกลียดเพราะการประมวลผลหลายตัวและการดองแตกและถูก จำกัด เว้นแต่คุณจะกระโดดออกจากไลบรารี่มาตรฐาน

หากคุณใช้การแยกที่multiprocessingเรียกว่าpathos.multiprocesssingคุณสามารถใช้คลาสและเมธอดคลาสโดยตรงในการmapทำงานของมัลติโพรเซสเซอร์ นี่เป็นเพราะdillใช้แทนpickleหรือcPickleและdillสามารถทำให้เป็นอนุกรมได้เกือบทุกอย่างในหลาม

pathos.multiprocessingยังมีฟังก์ชั่นแผนที่แบบอะซิงโครนัส ... และมันสามารถmapทำงานกับข้อโต้แย้งหลาย ๆ อย่าง (เช่นmap(math.pow, [1,2,3], [4,5,6]))

ดู: การ ประมวลผลหลายตัวและผักชีฝรั่งสามารถทำอะไรด้วยกันได้บ้าง

และ: 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]

และเพื่อให้ชัดเจนคุณสามารถทำสิ่งที่คุณต้องการในตอนแรกและคุณสามารถทำได้จากล่ามหากคุณต้องการ

>>> 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]
>>> 

รับรหัสได้ที่นี่: https://github.com/uqfoundation/pathos


3
คุณช่วยกรุณาอัปเดตคำตอบนี้ตาม pathos.pp เพราะ pathos.multiprocessing ไม่มีอยู่อีกต่อไปหรือไม่
Saheel Godhane

10
ฉันเป็นคนpathosเขียน รุ่นที่คุณอ้างถึงมีอายุหลายปี ลองรุ่นบน GitHub, คุณสามารถใช้pathos.ppหรือgithub.com/uqfoundation/ppft
Mike McKerns

1
หรือgithub.com/uqfoundation/pathos @SaheelGodhane: การเปิดตัวใหม่มีกำหนดเวลานาน แต่ควรจะออกในไม่ช้า
Mike McKerns

3
เป็นครั้งแรกแล้วpip install setuptools pip install git+https://github.com/uqfoundation/pathos.git@masterสิ่งนี้จะได้รับการอ้างอิงที่เหมาะสม รุ่นใหม่เกือบจะพร้อมแล้ว ... ตอนนี้เกือบทุกอย่างในpathosหน้าต่างจะ3.xทำงานและเข้ากันได้
Mike McKerns

1
@Rika: ใช่ มีการบล็อกแผนที่การทำซ้ำและ async
Mike McKerns

35

นอกจากนี้คุณยังสามารถกำหนด__call__()วิธีการภายในของคุณsomeClass()ซึ่งโทรsomeClass.go()แล้วผ่านตัวอย่างของsomeClass()การสระว่ายน้ำ วัตถุนี้ pickleable และใช้งานได้ดี (สำหรับฉัน) ...


3
นี่ง่ายกว่าเทคนิคที่เสนอโดย Alex Martelli แต่คุณ จำกัด การส่งเพียงหนึ่งวิธีต่อคลาสไปยังพูลหลายตัวประมวลผลของคุณ
เลิก

6
รายละเอียดอื่น ๆ ที่ควรคำนึงถึงคือมันเป็นเพียงวัตถุ (อินสแตนซ์ของคลาส) ที่ได้รับการดองไม่ใช่คลาสเอง ดังนั้นหากคุณเปลี่ยนแอตทริบิวต์คลาสใด ๆ จากค่าเริ่มต้นการเปลี่ยนแปลงเหล่านี้จะไม่แพร่กระจายไปยังกระบวนการอื่น วิธีแก้ปัญหาคือเพื่อให้แน่ใจว่าทุกสิ่งที่ฟังก์ชั่นของคุณต้องการได้รับการจัดเก็บเป็นแอตทริบิวต์ของอินสแตนซ์
เลิก

2
@dorvak คุณช่วยแสดงตัวอย่างง่ายๆด้วยได้ __call__()ไหม? ฉันคิดว่าคำตอบของคุณอาจจะดูดีกว่านี้ - ฉันกำลังพยายามเข้าใจข้อผิดพลาดนี้และเป็นครั้งแรกที่ฉันได้เห็นสาย คำตอบนี้ยังช่วยให้ชัดเจนว่าการประมวลผลหลายตัวทำอะไร: [ stackoverflow.com/a/20789937/305883]
user305883

1
คุณยกตัวอย่างสิ่งนี้ได้ไหม
frmsaul

1
มีคำตอบใหม่โพสต์ (ปัจจุบันอยู่ด้านล่างนี้) พร้อมโค้ดตัวอย่างสำหรับสิ่งนี้
Aaron

22

ข้อ จำกัด บางประการสำหรับโซลูชันของ Steven Bethard:

เมื่อคุณลงทะเบียนวิธีการเรียนของคุณเป็นฟังก์ชันตัวทำลายของชั้นเรียนของคุณจะถูกเรียกอย่างน่าประหลาดใจทุกครั้งที่การประมวลผลวิธีการของคุณเสร็จสิ้น ดังนั้นถ้าคุณมีคลาสอินสแตนซ์ของคุณ 1 ตัวที่เรียกใช้ n คูณเมธอดสมาชิกอาจหายไประหว่างการรัน 2 ครั้งและคุณอาจได้รับข้อความmalloc: *** error for object 0x...: pointer being freed was not allocated(เช่นไฟล์สมาชิกแบบเปิด) หรือpure virtual method called, terminate called without an active exception (ซึ่งหมายความว่าอายุการใช้งานของวัตถุสมาชิกที่ฉันใช้สั้นกว่า สิ่งที่ฉันคิด) ฉันได้สิ่งนี้เมื่อจัดการกับ n มากกว่าขนาดสระ นี่เป็นตัวอย่างสั้น ๆ :

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)

เอาท์พุท:

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__วิธีจะไม่เทียบเท่าเช่นนั้นเพราะ [ไม่มี ... ] อ่านจากผล:

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 !

ดังนั้นทั้งสองวิธีจึงไม่พอใจ ...


7
คุณจะได้รับNoneกลับเพราะนิยามของ__call__จะหายไปreturn: return self.process_obj(i)มันควรจะเป็น
torek

1
@Eric ฉันได้รับข้อผิดพลาดเดียวกันและฉันลองวิธีนี้ แต่ฉันเริ่มรับข้อผิดพลาดใหม่เป็น "cPickle.PicklingError: ไม่สามารถเลือก <type 'function'>: การค้นหาคุณลักษณะbuiltin .function ล้มเหลว" คุณรู้หรือไม่ว่าอะไรเป็นเหตุผลที่น่าจะเป็นไปได้
Naman

15

มีอีกทางลัดที่คุณสามารถใช้ได้แม้ว่ามันจะไม่มีประสิทธิภาพขึ้นอยู่กับว่ามีอะไรอยู่ในอินสแตนซ์ของคลาสของคุณ

อย่างที่ทุกคนบอกว่าปัญหาคือ 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)

ผลลัพธ์แสดงให้เห็นว่าจริง ๆ แล้วนวกรรมิกเรียกว่าหนึ่งครั้ง (ใน pid ดั้งเดิม) และ destructor เรียกว่า 9 ครั้ง (หนึ่งครั้งสำหรับแต่ละสำเนาที่ทำ = 2 หรือ 3 ครั้งต่อกระบวนการทำงานของสระว่ายน้ำตามที่ต้องการบวกหนึ่งครั้งในต้นฉบับ กระบวนการ). สิ่งนี้มักจะตกลงเช่นเดียวกับในกรณีนี้เนื่องจากตัวเลือกเริ่มต้นสร้างสำเนาของอินสแตนซ์ทั้งหมดและ (กึ่ง -) แอบเติมข้อมูลอีกครั้งอย่างลับๆ - ในกรณีนี้ให้ทำ:

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

- นั่นเป็นเหตุผลว่าทำไมถึงแม้ว่าตัวทำลายจะถูกเรียกแปดครั้งในกระบวนการของผู้ปฏิบัติงานสามคนมันนับถอยหลังจาก 1 ถึง 0 ในแต่ละครั้ง - แต่แน่นอนว่าคุณยังคงมีปัญหาด้วยวิธีนี้ หากจำเป็นคุณสามารถระบุของคุณเอง__setstate__:

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

ในกรณีนี้เช่น


1
นี่เป็นคำตอบที่ดีที่สุดสำหรับปัญหานี้เนื่องจากเป็นวิธีที่ง่ายที่สุดในการใช้กับพฤติกรรมเริ่มต้นที่ไม่สามารถดองได้
Matt Taylor

12

นอกจากนี้คุณยังสามารถกำหนด__call__()วิธีการภายในของคุณsomeClass()ซึ่งโทรsomeClass.go()แล้วผ่านตัวอย่างของsomeClass()การสระว่ายน้ำ วัตถุนี้ pickleable และใช้งานได้ดี (สำหรับฉัน) ...

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

ทางออกจากparisjohnด้านบนใช้ได้ดีกับฉัน บวกกับรหัสที่ดูสะอาดตาและเข้าใจง่าย ในกรณีของฉันมีฟังก์ชั่นบางอย่างสำหรับการโทรโดยใช้พูลดังนั้นฉันจึงแก้ไขโค้ดของ parisjohn เล็กน้อยด้านล่าง ฉันได้ทำการเรียกเพื่อให้สามารถเรียกใช้ฟังก์ชันต่าง ๆ และชื่อฟังก์ชันถูกส่งผ่านในอาร์กิวเมนต์ dict จาก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

วิธีแก้ปัญหาที่อาจเกิดขึ้นกับสิ่งนี้คือการเปลี่ยนไปใช้ multiprocessing.dummyวิธีการแก้ปัญหาที่อาจเกิดขึ้นเล็กน้อยเพื่อการนี้คือการเปลี่ยนไปใช้นี่เป็นการนำเธรดไปใช้ของอินเทอร์เฟซการประมวลผลหลายอย่างที่ดูเหมือนจะไม่มีปัญหาใน Python 2.7 ฉันไม่มีประสบการณ์มากมายที่นี่ แต่การเปลี่ยนแปลงการนำเข้าอย่างรวดเร็วทำให้ฉันสามารถเรียกใช้ Apply_async ในวิธีการเรียนได้

แหล่งข้อมูลที่ดีบางประการเกี่ยวกับmultiprocessing.dummy:

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

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


1

ในกรณีง่าย ๆ นี้ที่someClass.fไม่ได้รับข้อมูลใด ๆ จากชั้นเรียนและไม่ได้แนบอะไรกับชั้นเรียนวิธีแก้ปัญหาที่เป็นไปได้จะแยกออกจากกัน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

ทำไมไม่ใช้ func ต่างหากล่ะ?

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

print pool.map(func, arr)

1

ฉันพบปัญหาเดียวกันนี้ แต่พบว่ามีตัวเข้ารหัส JSON ที่สามารถใช้เพื่อย้ายวัตถุเหล่านี้ระหว่างกระบวนการ

from pyVmomi.VmomiSupport import VmomiJSONEncoder

ใช้สิ่งนี้เพื่อสร้างรายการของคุณ:

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

จากนั้นในฟังก์ชันที่แมปให้ใช้สิ่งนี้เพื่อกู้คืนวัตถุ:

pfVmomiObj = json.loads(jsonSerialized)

0

อัปเดต: ณ วันที่เขียนนี้ namedTuples สามารถเลือกได้ (เริ่มต้นด้วย python 2.7)

ปัญหาที่นี่คือกระบวนการลูกไม่สามารถนำเข้าคลาสของวัตถุ - ในกรณีนี้คลาส P- ในกรณีของโครงการที่มีหลายรุ่นคลาส P ควรนำเข้าได้ทุกที่ที่กระบวนการลูกใช้

วิธีแก้ปัญหาอย่างรวดเร็วคือทำให้สามารถนำเข้าได้โดยส่งผลกระทบต่อ globals ()

globals()["P"] = P
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.