Không thể chọn <gõ 'instancemethod'> khi sử dụng đa xử lý Pool.map ()


218

Tôi đang cố gắng sử dụng multiprocessingcủa Pool.map()chức năng để phân chia công việc cùng một lúc. Khi tôi sử dụng đoạn mã sau, nó hoạt động tốt:

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

Tuy nhiên, khi tôi sử dụng nó theo cách tiếp cận hướng đối tượng hơn, nó không hoạt động. Thông báo lỗi mà nó đưa ra là:

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

Điều này xảy ra khi sau đây là chương trình chính của tôi:

import someClass

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

và sau đây là someClasslớp học của tôi :

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

Bất cứ ai cũng biết vấn đề có thể là gì, hoặc một cách dễ dàng xung quanh nó?


4
nếu f là một hàm lồng nhau thì có một lỗi tương tựPicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
ggg

Câu trả lời:


122

Vấn đề là đa xử lý phải chọn mọi thứ để đưa chúng vào giữa các quy trình và các phương thức ràng buộc không thể chọn được. Cách giải quyết (cho dù bạn có coi nó là "dễ dàng" hay không ;-) là thêm cơ sở hạ tầng vào chương trình của bạn để cho phép các phương thức đó được chọn, đăng ký nó với phương thức thư viện chuẩn copy_reg .

Ví dụ, đóng góp của Steven Bethard cho chủ đề này (đến cuối chuỗi) cho thấy một cách tiếp cận hoàn toàn khả thi để cho phép chọn / tháo gỡ phương thức thông qua copy_reg.


Thật tuyệt vời, cảm ơn bạn. Dường như đã tiến triển theo một cách nào đó, dù sao đi nữa: Sử dụng mã tại pastebin.ca/1693348 Bây giờ tôi nhận được RuntimeError: vượt quá độ sâu đệ quy tối đa. Tôi nhìn xung quanh và một bài đăng trên diễn đàn đề nghị tăng độ sâu tối đa lên 1500 (từ 1000 mặc định) nhưng tôi không có niềm vui ở đó. Thành thật mà nói, tôi không thể thấy phần nào (ít nhất là mã của tôi) có thể được đệ quy ngoài tầm kiểm soát, trừ khi vì một lý do nào đó mà mã bị vướng và tháo ra trong một vòng lặp, do những thay đổi nhỏ tôi đã thực hiện để thực hiện Mã của Steven OO'd?
ventolin

1
_pickle_methodLợi nhuận của bạn self._unpickle_method, một phương pháp ràng buộc; vì vậy, dĩ nhiên, dưa chua hiện đang cố gắng chọn THAT - và nó thực hiện như bạn đã nói với nó: bằng cách gọi _pickle_method, đệ quy. Tức là bằng cách OOnhập mã theo cách này, bạn chắc chắn đã giới thiệu đệ quy vô hạn. Tôi khuyên bạn nên quay lại mã của Steven (và không thờ cúng tại bàn thờ của OO khi không phù hợp: nhiều thứ trong Python được thực hiện tốt nhất theo cách nhiều chức năng hơn, và đây là một).
Alex Martelli


15
Đối với siêu siêu lười biếng , hãy xem câu trả lời duy nhất làm phiền để đăng mã không bị
xáo trộn

2
Một cách khác để khắc phục / khắc phục sự cố tẩy là sử dụng thì là, hãy xem câu trả lời của tôi stackoverflow.com/questions/8804830/
Kẻ

74

Tất cả các giải pháp này đều xấu vì đa xử lý và tẩy rửa bị hỏng và giới hạn trừ khi bạn nhảy ra ngoài thư viện tiêu chuẩn.

Nếu bạn sử dụng một nhánh multiprocessinggọi pathos.multiprocesssing, bạn có thể trực tiếp sử dụng các lớp và phương thức lớp trong các maphàm đa xử lý . Điều này là do dillđược sử dụng thay vì picklehoặc cPickle, và dillcó thể tuần tự hóa hầu hết mọi thứ trong python.

pathos.multiprocessingcũng cung cấp một hàm bản đồ không đồng bộ, và nó có thể maphoạt động với nhiều đối số (ví dụ map(math.pow, [1,2,3], [4,5,6]))

Xem: Điều gì có thể đa xử lý và thì là làm gì với nhau?

và: 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]

Và để rõ ràng, bạn có thể thực hiện chính xác muốn bạn muốn làm ngay từ đầu và bạn có thể làm điều đó từ trình thông dịch, nếu bạn muốn.

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

Nhận mã tại đây: https://github.com/uqfoundation/pathos


3
Bạn có thể vui lòng cập nhật câu trả lời này dựa trên pathos.pp vì pathos.multiprocessing không còn tồn tại nữa không?
Saheel Godhane

10
Tôi là pathostác giả. Phiên bản mà bạn đang đề cập là vài năm tuổi. Hãy thử phiên bản trên github, Bạn có thể sử dụng pathos.pphoặc github.com/uqfoundation/ppft .
Mike McKerns

1
hoặc github.com/uqfoundation/pathos . @SaheelGodhane: Một bản phát hành mới đã quá hạn, nhưng sẽ sớm ra mắt.
Mike McKerns

3
Đầu tiên pip install setuptools, sau đó pip install git+https://github.com/uqfoundation/pathos.git@master. Điều này sẽ có được sự phụ thuộc thích hợp. Một bản phát hành mới gần như đã sẵn sàng, giờ đây hầu như mọi thứ đều pathoschạy trên windows và 3.xtương thích.
Mike McKerns 24/07/2015

1
@Rika: Vâng. bản đồ chặn, lặp và không đồng bộ có sẵn.
Mike McKerns

35

Bạn cũng có thể định nghĩa một __call__()phương thức bên trong someClass(), gọi someClass.go()và sau đó chuyển một thể hiện của someClass()nhóm. Đối tượng này có thể chọn và nó hoạt động tốt (đối với tôi) ...


3
Điều này dễ hơn nhiều so với kỹ thuật do Alex Martelli đề xuất, nhưng bạn bị giới hạn chỉ gửi một phương thức cho mỗi lớp đến nhóm đa xử lý của mình.
không dùng nữa

6
Một chi tiết khác cần lưu ý là nó chỉ là đối tượng (thể hiện của lớp) được ngâm, chứ không phải chính lớp đó. Do đó, nếu bạn đã thay đổi bất kỳ thuộc tính lớp nào từ các giá trị mặc định của chúng, những thay đổi này sẽ không lan truyền đến các quy trình khác nhau. Giải pháp thay thế là đảm bảo rằng mọi thứ mà chức năng của bạn cần được lưu trữ dưới dạng một thuộc tính thể hiện.
không dùng nữa

2
@dorvak bạn có thể vui lòng chỉ ra một ví dụ đơn giản với __call__()? Tôi nghĩ rằng câu trả lời của bạn có thể là câu trả lời rõ ràng hơn - tôi đang vật lộn để hiểu lỗi này và lần đầu tiên tôi đến để xem cuộc gọi. Nhân tiện, câu trả lời này cũng giúp làm rõ việc đa xử lý làm gì: [ stackoverflow.com/a/20789937/305883]
user305883

1
Bạn có thể cho một ví dụ về điều này?
frmsaul

1
Có một câu trả lời mới được đăng (hiện dưới mức này) với mã ví dụ cho việc này.
Aaron

22

Một số hạn chế mặc dù đối với giải pháp của Steven Bethard:

Khi bạn đăng ký phương thức lớp của bạn như là một hàm, hàm hủy của lớp của bạn được gọi một cách đáng ngạc nhiên mỗi khi quá trình xử lý phương thức của bạn kết thúc. Vì vậy, nếu bạn có 1 thể hiện của lớp gọi n lần phương thức của nó, các thành viên có thể biến mất giữa 2 lần chạy và bạn có thể nhận được một tin nhắn malloc: *** error for object 0x...: pointer being freed was not allocated(ví dụ: tệp thành viên mở) hoặc pure virtual method called, terminate called without an active exception(có nghĩa là thời gian tồn tại của một đối tượng thành viên tôi đã sử dụng ngắn hơn những gì tôi nghĩ). Tôi đã nhận được điều này khi giao dịch với n lớn hơn kích thước hồ bơi. Đây là một ví dụ ngắ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)

Đầu ra:

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

Các __call__phương pháp không phải là quá tương đương, vì [Không ...] được đọc từ các kết quả:

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 !

Vì vậy, không có cả hai phương pháp đều thỏa mãn ...


7
Bạn Nonequay lại vì định nghĩa của bạn __call__bị thiếu return: nó nên như vậy return self.process_obj(i).

1
@Eric Tôi cũng gặp lỗi tương tự và tôi đã thử giải pháp này, tuy nhiên tôi bắt đầu gặp lỗi mới là "cPickle.PicklingError: Không thể chọn <type 'function'>: tra cứu thuộc tính dựng sẵn. Lỗi chức năng ". Bạn có biết những gì có thể là một lý do có thể xảy ra đằng sau nó?
Naman

15

Có một cách rút gọn khác mà bạn có thể sử dụng, mặc dù nó có thể không hiệu quả tùy thuộc vào những gì trong các thể hiện trong lớp của bạn.

Như mọi người đã nói vấn đề là multiprocessingmã phải chọn những thứ mà nó gửi đến các quy trình con mà nó đã bắt đầu và bộ chọn không thực hiện các phương thức cá thể.

Tuy nhiên, thay vì gửi phương thức cá thể, bạn có thể gửi cá thể lớp thực tế, cộng với tên của hàm để gọi, đến một hàm thông thường sau đó sử dụng getattrđể gọi phương thức cá thể, do đó tạo ra phương thức ràng buộc trong quy Pooltrình con. Điều này tương tự với việc xác định một __call__phương thức ngoại trừ việc bạn có thể gọi nhiều hơn một hàm thành viên.

Đánh cắp mã của @ EricH. Từ câu trả lời của anh ấy và chú thích nó một chút (tôi đã gõ lại do đó tất cả các tên thay đổi và vì vậy, vì một số lý do, điều này có vẻ dễ hơn cắt và dán :-)) để minh họa cho tất cả các phép thuật:

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)

Kết quả đầu ra cho thấy, trên thực tế, hàm tạo được gọi một lần (trong pid gốc) và hàm hủy được gọi 9 lần (một lần cho mỗi bản sao được thực hiện = 2 hoặc 3 lần cho mỗi tiến trình pool-worker khi cần, cộng với một lần trong bản gốc quá trình). Điều này thường ổn, như trong trường hợp này, vì trình chọn mặc định tạo một bản sao của toàn bộ thể hiện và (bán) bí mật điền lại nó trong trường hợp này, thực hiện:

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

Tại sao mặc dù bộ hủy được gọi tám lần trong ba quy trình worker, nó đếm ngược từ 1 đến 0 mỗi lần, nhưng tất nhiên bạn vẫn có thể gặp rắc rối theo cách này. Nếu cần thiết, bạn có thể cung cấp của riêng bạn __setstate__:

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

trong trường hợp này chẳng hạn.


1
Đây là câu trả lời tốt nhất cho vấn đề này, vì đây là cách dễ nhất để áp dụng cho hành vi mặc định không thể chọn được
Matt Taylor

12

Bạn cũng có thể định nghĩa một __call__()phương thức bên trong someClass(), gọi someClass.go()và sau đó chuyển một thể hiện của someClass()nhóm. Đối tượng này có thể chọn và nó hoạt động tốt (đối với tôi) ...

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

Giải pháp từ parisjohn ở trên hoạt động tốt với tôi. Cộng với mã trông sạch sẽ và dễ hiểu. Trong trường hợp của tôi, có một vài chức năng để gọi bằng Pool, vì vậy tôi đã sửa đổi mã của parisjohn một chút bên dưới. Tôi đã thực hiện cuộc gọi để có thể gọi một số hàm và tên hàm được truyền trong đối số dict từ 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

Một giải pháp tầm thường cho việc này là chuyển sang sử dụng multiprocessing.dummy. Đây là một triển khai dựa trên luồng của giao diện đa xử lý dường như không có vấn đề này trong Python 2.7. Tôi không có nhiều kinh nghiệm ở đây, nhưng thay đổi nhập nhanh này cho phép tôi gọi application_async theo phương thức lớp.

Một vài tài nguyên tốt về multiprocessing.dummy:

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

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


1

Trong trường hợp đơn giản này, khi someClass.fkhông kế thừa bất kỳ dữ liệu nào từ lớp và không đính kèm bất cứ thứ gì vào lớp, một giải pháp khả thi sẽ là tách ra f, vì vậy nó có thể được xử lý:

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

Tại sao không sử dụng func riêng?

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

print pool.map(func, arr)

1

Tôi gặp vấn đề tương tự nhưng phát hiện ra rằng có một bộ mã hóa JSON có thể được sử dụng để di chuyển các đối tượng này giữa các quy trình.

from pyVmomi.VmomiSupport import VmomiJSONEncoder

Sử dụng điều này để tạo danh sách của bạn:

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

Sau đó, trong chức năng ánh xạ, sử dụng điều này để phục hồi đối tượng:

pfVmomiObj = json.loads(jsonSerialized)

0

Cập nhật: kể từ ngày viết bài này, tênTuples có thể được chọn (bắt đầu với python 2.7)

Vấn đề ở đây là các tiến trình con không thể nhập lớp của đối tượng - trong trường hợp này, lớp P-, trong trường hợp của một dự án đa mô hình, Lớp P phải được nhập vào bất cứ nơi nào mà tiến trình con được sử dụng

một cách giải quyết nhanh là làm cho nó có thể nhập được bằng cách tác động đến toàn cầu ()

globals()["P"] = P
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.