มัลติโพรเซสซิง: วิธีใช้ Pool.map กับฟังก์ชั่นที่กำหนดในคลาสได้อย่างไร?


179

เมื่อฉันเรียกใช้บางสิ่งเช่น:

from multiprocessing import Pool

p = Pool(5)
def f(x):
     return x*x

p.map(f, [1,2,3])

มันใช้งานได้ดี อย่างไรก็ตามการวางสิ่งนี้เป็นฟังก์ชันของคลาส:

class calculate(object):
    def run(self):
        def f(x):
            return x*x

        p = Pool()
        return p.map(f, [1,2,3])

cl = calculate()
print cl.run()

ทำให้ฉันมีข้อผิดพลาดต่อไปนี้:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/sw/lib/python2.6/threading.py", line 532, in __bootstrap_inner
    self.run()
  File "/sw/lib/python2.6/threading.py", line 484, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/sw/lib/python2.6/multiprocessing/pool.py", line 225, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

ฉันเห็นโพสต์จาก Alex Martelli จัดการกับปัญหาแบบเดียวกัน แต่ก็ไม่ชัดเจนพอ


1
"นี่เป็นฟังก์ชันของคลาส" หรือไม่ คุณช่วยโพสต์โค้ดที่ได้รับข้อผิดพลาดจริงได้ไหม หากไม่มีรหัสจริงเราสามารถเดาได้ว่าคุณทำอะไรผิด
S.Lott

ตามที่กล่าวไว้ทั่วไปมีโมดูลการเก็บรักษาที่มีประสิทธิภาพมากกว่าโมดูลดองของ Python (เช่นโมดูลpicloud ที่กล่าวถึงในคำตอบนี้ )
klaus se

1
ฉันมีปัญหาคล้ายกันกับการปิดIPython.Parallelแต่คุณสามารถแก้ไขปัญหาได้โดยการผลักวัตถุไปยังโหนด ดูเหมือนว่าค่อนข้างน่ารำคาญที่จะแก้ไขปัญหาด้วยการประมวลผลหลายตัว
Alex S

นี่calculateคือ picklable จึงดูเหมือนว่าเช่นนี้สามารถแก้ไขได้โดย 1) การสร้างวัตถุฟังก์ชั่นคอนสตรัคที่สำเนากว่าcalculateตัวอย่างแล้ว 2) ผ่านอินสแตนซ์ของวัตถุฟังก์ชันนี้เพื่อPool's mapวิธี ไม่มี?
rd11

1
@ แมทฉันไม่เชื่อว่าการเปลี่ยนแปลงล่าสุดของไพ ธ อนจะช่วยอะไรได้ ข้อ จำกัด บางประการของmultiprocessingโมดูลเกิดจากเป้าหมายของการใช้งานข้ามแพลตฟอร์มและการขาดการfork(2)เรียกระบบที่เหมือนกันใน Windows หากคุณไม่สนใจเกี่ยวกับการสนับสนุน Win32 อาจมีวิธีการแก้ไขตามกระบวนการที่ง่ายขึ้น หรือหากคุณกำลังเตรียมที่จะใช้หัวข้อแทนของกระบวนการคุณสามารถใช้แทนด้วยfrom multiprocessing import Pool from multiprocessing.pool import ThreadPool as Pool
Aya

คำตอบ:


69

ฉันยังรำคาญด้วยข้อ จำกัด เกี่ยวกับฟังก์ชั่นประเภท pool.map ที่สามารถยอมรับได้ ฉันเขียนสิ่งต่อไปนี้เพื่อหลีกเลี่ยงสิ่งนี้ ดูเหมือนว่าจะใช้งานได้แม้ใช้ parmap แบบเรียกซ้ำ

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(pipe, x):
        pipe.send(f(x))
        pipe.close()
    return fun

def parmap(f, X):
    pipe = [Pipe() for x in X]
    proc = [Process(target=spawn(f), args=(c, x)) for x, (p, c) in izip(X, pipe)]
    [p.start() for p in proc]
    [p.join() for p in proc]
    return [p.recv() for (p, c) in pipe]

if __name__ == '__main__':
    print parmap(lambda x: x**x, range(1, 5))

1
มันใช้งานได้ดีมากสำหรับฉันขอบคุณ ฉันพบข้อบกพร่องหนึ่งข้อ: ฉันลองใช้ parmap กับฟังก์ชั่นบางอย่างที่ส่งผ่านค่าเริ่มต้นและรับ PicklingError อีกครั้ง ฉันไม่ได้คิดวิธีแก้ปัญหานี้ฉันเพิ่งทำใหม่รหัสของฉันเพื่อไม่ใช้ defaultdict
sans

2
สิ่งนี้ไม่ทำงานใน Python 2.7.2 (ค่าเริ่มต้น, 12 มิ.ย. 2011, 15:08:59) [MSC v.1500 32 บิต (Intel)] ใน win32
ubershmekel

3
สิ่งนี้ใช้ได้กับ Python 2.7.3 ส.ค. 1,2012, 05:14:39 สิ่งนี้ไม่สามารถใช้งานกับ iterables ขนาดยักษ์ -> มันทำให้ OSError: [Errno 24] มีไฟล์เปิดมากเกินไปเนื่องจากจำนวนของ pipe ที่เปิดขึ้น
Eiyrioü von Kauyf

วิธีนี้จะสร้างกระบวนการสำหรับแต่ละรายการงาน การแก้ปัญหาของ "klaus se" ด้านล่างมีประสิทธิภาพมากขึ้น
ypnos

85

ฉันไม่สามารถใช้รหัสที่โพสต์ได้เนื่องจากรหัสที่ใช้ "multiprocessing.Pool" ไม่สามารถใช้กับนิพจน์แลมบ์ดาและรหัสที่ไม่ได้ใช้ "multiprocessing.Pool" วางไข่ได้มากเท่าที่มีรายการงาน

ฉันปรับรหัสเพื่อให้เกิดจำนวนพนักงานที่กำหนดไว้ล่วงหน้าและวนซ้ำผ่านรายการอินพุตหากมีผู้ทำงานว่าง ฉันยังเปิดใช้งานโหมด "daemon" สำหรับคนงาน st ctrl-c ตามที่คาดไว้

import multiprocessing


def fun(f, q_in, q_out):
    while True:
        i, x = q_in.get()
        if i is None:
            break
        q_out.put((i, f(x)))


def parmap(f, X, nprocs=multiprocessing.cpu_count()):
    q_in = multiprocessing.Queue(1)
    q_out = multiprocessing.Queue()

    proc = [multiprocessing.Process(target=fun, args=(f, q_in, q_out))
            for _ in range(nprocs)]
    for p in proc:
        p.daemon = True
        p.start()

    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [q_in.put((None, None)) for _ in range(nprocs)]
    res = [q_out.get() for _ in range(len(sent))]

    [p.join() for p in proc]

    return [x for i, x in sorted(res)]


if __name__ == '__main__':
    print(parmap(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8]))

2
คุณจะได้รับแถบความคืบหน้าในการทำงานอย่างถูกต้องกับparmapฟังก์ชั่นนี้ได้อย่างไร?
# #

2
คำถาม - ฉันใช้วิธีนี้ แต่สังเกตว่ากระบวนการของหลามที่ฉันวางไข่ยังคงใช้งานอยู่ในหน่วยความจำ มีความคิดอย่างรวดเร็วเกี่ยวกับวิธีการฆ่าเหล่านั้นเมื่อ parmap ของคุณออก?
CompEcon

1
@ klaus-se ฉันรู้ว่าเราหมดกำลังใจจากการพูดขอบคุณในความคิดเห็น แต่คำตอบของคุณมีค่ามากเกินไปสำหรับฉันฉันไม่สามารถต้านทานได้ ฉันหวังว่าฉันจะให้คุณมากกว่าหนึ่งชื่อเสียง ...
deshtop

2
@greole ผ่าน(None, None)รายการสุดท้ายบ่งบอกfunว่ามันถึงจุดสิ้นสุดของลำดับของรายการสำหรับแต่ละกระบวนการ
aganders3

4
@deshtop: คุณสามารถรับรางวัลได้ถ้าคุณมีชื่อเสียงเพียงพอด้วยตัวคุณเอง :-)
ทำเครื่องหมาย

57

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

หากคุณใช้การแยกที่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

มันจัดการกับรหัสที่คุณเขียนในตอนแรกโดยไม่มีการดัดแปลงและจากล่าม ทำไมอย่างอื่นที่มีความเปราะบางและเฉพาะเจาะจงมากกว่าในกรณีเดียว?

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> class calculate(object):
...  def run(self):
...   def f(x):
...    return x*x
...   p = Pool()
...   return p.map(f, [1,2,3])
... 
>>> cl = calculate()
>>> print cl.run()
[1, 4, 9]

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

และเพียงเพื่ออวดสิ่งที่ทำได้:

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> 
>>> p = Pool(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]
>>> 
>>> res = p.amap(t.plus, x, y)
>>> res.get()
[4, 6, 8, 10]

1
pathos.multiprocessing ยังมีแผนที่แบบอะซิงโครนัส ( amap) ที่เปิดใช้งานแถบความคืบหน้าและการเขียนโปรแกรมแบบอะซิงโครนัสอื่น ๆ
Mike McKerns

ฉันชอบ pathos.multiprocessing ซึ่งสามารถทำหน้าที่แทนการแมปแบบไม่ขนานในขณะที่เพลิดเพลินกับการประมวลผลหลายตัว ฉันมี wrapper ง่าย ๆ ของ pathos.multiprocessing.map เช่นนั้นจะมีหน่วยความจำที่มีประสิทธิภาพมากขึ้นเมื่อประมวลผลโครงสร้างข้อมูลขนาดใหญ่แบบอ่านอย่างเดียวในหลายคอร์ดูที่เก็บ gitนี้
Fashandge

ดูเหมือนน่าสนใจ แต่ไม่ได้ติดตั้ง นี่คือ pip ข้อความให้:Could not find a version that satisfies the requirement pp==1.5.7-pathos (from pathos)
xApple

1
ใช่. ฉันยังไม่ได้เปิดตัวในขณะที่ฉันได้แยกฟังก์ชั่นการทำงานออกเป็นแพ็คเกจแยกต่างหากและยังแปลงเป็นรหัสที่เข้ากันได้ 2/3 ส่วนใหญ่ข้างต้นได้รับการทำให้เป็นโมดูลmultiprocessซึ่งเข้ากันได้ 2/3 ดูstackoverflow.com/questions/27873093/...และpypi.python.org/pypi/multiprocess
Mike McKerns

3
@xApple: เช่นเดียวกับการติดตามpathosมีการเปิดตัวที่มั่นคงใหม่และยังเป็นที่รองรับ 2.x และ 3.x
Mike McKerns

40

ขณะนี้ยังไม่มีวิธีแก้ไขปัญหาของคุณเท่าที่ฉันทราบ: ฟังก์ชั่นที่คุณให้map()ต้องสามารถเข้าถึงได้ผ่านการนำเข้าโมดูลของคุณ นี่คือสาเหตุที่รหัสของโรเบิร์ตทำงาน: ฟังก์ชั่นf()สามารถรับได้โดยการนำเข้ารหัสต่อไปนี้:

def f(x):
    return x*x

class Calculate(object):
    def run(self):
        p = Pool()
        return p.map(f, [1,2,3])

if __name__ == '__main__':
    cl = Calculate()
    print cl.run()

ฉันเพิ่มส่วน "main" จริงเนื่องจากเป็นไปตามคำแนะนำสำหรับแพลตฟอร์ม Windows ("ตรวจสอบให้แน่ใจว่าโมดูลหลักสามารถนำเข้าได้อย่างปลอดภัยโดยล่าม Python ใหม่โดยไม่ทำให้เกิดผลข้างเคียงโดยไม่ได้ตั้งใจ")

ฉันยังเพิ่มอักษรตัวพิมพ์ใหญ่ในด้านหน้าของCalculateเพื่อที่จะปฏิบัติตามPEP 8 :)


18

การแก้ปัญหาโดย mrule ถูกต้อง แต่มีข้อผิดพลาด: ถ้าเด็กส่งกลับข้อมูลจำนวนมากก็สามารถกรอกบัฟเฟอร์ท่อ, การปิดกั้นบนของเด็กในขณะที่พ่อแม่กำลังรอให้เด็กที่จะทางออกpipe.send() pipe.join()ทางออกคือการอ่านข้อมูลของเด็กก่อนjoin()นำเด็ก นอกจากนี้เด็กควรปิดปลายท่อของพ่อแม่เพื่อป้องกันการหยุดชะงัก รหัสด้านล่างแก้ไขได้ นอกจากนี้ยังทราบว่านี้สร้างกระบวนการต่อหนึ่งองค์ประกอบในparmap Xโซลูชันขั้นสูงเพิ่มเติมคือการใช้multiprocessing.cpu_count()เพื่อแบ่งXจำนวนชิ้นแล้วผสานผลลัพธ์ก่อนส่งคืน ฉันปล่อยให้มันเป็นแบบฝึกหัดให้กับผู้อ่านเพื่อไม่ให้เสียความกระชับของคำตอบที่ดีโดย mrule ;)

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(ppipe, cpipe,x):
        ppipe.close()
        cpipe.send(f(x))
        cpipe.close()
    return fun

def parmap(f,X):
    pipe=[Pipe() for x in X]
    proc=[Process(target=spawn(f),args=(p,c,x)) for x,(p,c) in izip(X,pipe)]
    [p.start() for p in proc]
    ret = [p.recv() for (p,c) in pipe]
    [p.join() for p in proc]
    return ret

if __name__ == '__main__':
    print parmap(lambda x:x**x,range(1,5))

คุณจะเลือกจำนวนกระบวนการได้อย่างไร
patapouf_ai

OSError: [Errno 24] Too many open filesแต่มันจะตายสวยได้อย่างรวดเร็วเนื่องจากข้อผิดพลาด ผมคิดว่ามีจะต้องมีการจัดเรียงของข้อ จำกัด เกี่ยวกับจำนวนของกระบวนการบางอย่างเพื่อให้การทำงานอย่างถูกต้อง ...
patapouf_ai

13

ฉันยังดิ้นรนกับสิ่งนี้ ฉันมีฟังก์ชั่นเป็นสมาชิกข้อมูลของคลาสเป็นตัวอย่างง่าย ๆ :

from multiprocessing import Pool
import itertools
pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # Needed to do something like this (the following line won't work)
        return pool.map(self.f,list1,list2)  

ฉันต้องการใช้ฟังก์ชัน self.f ในการเรียก Pool.map () จากภายในคลาสเดียวกันและ self.f ไม่ใช้ tuple เป็นอาร์กิวเมนต์ เนื่องจากฟังก์ชั่นนี้ฝังอยู่ในชั้นเรียนจึงไม่ชัดเจนสำหรับฉันที่จะเขียนประเภทของคำตอบที่แนะนำ

ฉันแก้ไขปัญหานี้โดยใช้ wrapper ที่แตกต่างกันซึ่งรับ tuple / list โดยที่ element แรกคือ function และองค์ประกอบที่เหลือคืออาร์กิวเมนต์ของฟังก์ชันนั้นที่เรียกว่า eval_func_tuple (f_args) การใช้สิ่งนี้บรรทัดที่มีปัญหาสามารถถูกแทนที่ด้วย return pool.map (eval_func_tuple, itertools.izip (itertools.repeat (self.f), list1, list2)) นี่คือรหัสเต็ม:

ไฟล์: util.py

def add(a, b): return a+b

def eval_func_tuple(f_args):
    """Takes a tuple of a function and args, evaluates and returns result"""
    return f_args[0](*f_args[1:])  

ไฟล์: main.py

from multiprocessing import Pool
import itertools
import util  

pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # The following line will now work
        return pool.map(util.eval_func_tuple, 
            itertools.izip(itertools.repeat(self.f), list1, list2)) 

if __name__ == '__main__':
    myExample = Example(util.add)
    list1 = [1, 2, 3]
    list2 = [10, 20, 30]
    print myExample.add_lists(list1, list2)  

การเรียกใช้ main.py จะให้ [11, 22, 33] อย่าลังเลที่จะปรับปรุงสิ่งนี้ตัวอย่างเช่น eval_func_tuple ยังสามารถแก้ไขเพื่อรับอาร์กิวเมนต์คำหลักได้

ในหมายเหตุอื่นในคำตอบอื่น ๆ ฟังก์ชั่น "parmap" สามารถทำให้มีประสิทธิภาพมากขึ้นสำหรับกรณีของกระบวนการมากขึ้นกว่าจำนวนของ CPU ที่มีอยู่ ฉันกำลังคัดลอกเวอร์ชันที่แก้ไขด้านล่าง นี่คือโพสต์แรกของฉันและฉันไม่แน่ใจว่าควรแก้ไขคำตอบเดิมโดยตรงหรือไม่ ฉันยังเปลี่ยนชื่อตัวแปรบางอย่าง

from multiprocessing import Process, Pipe  
from itertools import izip  

def spawn(f):  
    def fun(pipe,x):  
        pipe.send(f(x))  
        pipe.close()  
    return fun  

def parmap(f,X):  
    pipe=[Pipe() for x in X]  
    processes=[Process(target=spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]  
    numProcesses = len(processes)  
    processNum = 0  
    outputList = []  
    while processNum < numProcesses:  
        endProcessNum = min(processNum+multiprocessing.cpu_count(), numProcesses)  
        for proc in processes[processNum:endProcessNum]:  
            proc.start()  
        for proc in processes[processNum:endProcessNum]:  
            proc.join()  
        for proc,c in pipe[processNum:endProcessNum]:  
            outputList.append(proc.recv())  
        processNum = endProcessNum  
    return outputList    

if __name__ == '__main__':  
    print parmap(lambda x:x**x,range(1,5))         

8

ฉันใช้คำตอบของ klaus se และ aganders3 และทำโมดูลเอกสารที่อ่านได้ง่ายกว่าและเก็บไว้ในไฟล์เดียว คุณสามารถเพิ่มลงในโครงการของคุณได้ มันยังมีแถบความคืบหน้าเพิ่มเติม!

"""
The ``processes`` module provides some convenience functions
for using parallel processes in python.

Adapted from http://stackoverflow.com/a/16071616/287297

Example usage:

    print prll_map(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8], 32, verbose=True)

Comments:

"It spawns a predefined amount of workers and only iterates through the input list
 if there exists an idle worker. I also enabled the "daemon" mode for the workers so
 that KeyboardInterupt works as expected."

Pitfalls: all the stdouts are sent back to the parent stdout, intertwined.

Alternatively, use this fork of multiprocessing: 
https://github.com/uqfoundation/multiprocess
"""

# Modules #
import multiprocessing
from tqdm import tqdm

################################################################################
def apply_function(func_to_apply, queue_in, queue_out):
    while not queue_in.empty():
        num, obj = queue_in.get()
        queue_out.put((num, func_to_apply(obj)))

################################################################################
def prll_map(func_to_apply, items, cpus=None, verbose=False):
    # Number of processes to use #
    if cpus is None: cpus = min(multiprocessing.cpu_count(), 32)
    # Create queues #
    q_in  = multiprocessing.Queue()
    q_out = multiprocessing.Queue()
    # Process list #
    new_proc  = lambda t,a: multiprocessing.Process(target=t, args=a)
    processes = [new_proc(apply_function, (func_to_apply, q_in, q_out)) for x in range(cpus)]
    # Put all the items (objects) in the queue #
    sent = [q_in.put((i, x)) for i, x in enumerate(items)]
    # Start them all #
    for proc in processes:
        proc.daemon = True
        proc.start()
    # Display progress bar or not #
    if verbose:
        results = [q_out.get() for x in tqdm(range(len(sent)))]
    else:
        results = [q_out.get() for x in range(len(sent))]
    # Wait for them to finish #
    for proc in processes: proc.join()
    # Return results #
    return [x for i, x in sorted(results)]

################################################################################
def test():
    def slow_square(x):
        import time
        time.sleep(2)
        return x**2
    objs    = range(20)
    squares = prll_map(slow_square, objs, 4, verbose=True)
    print "Result: %s" % squares

แก้ไข : เพิ่มคำแนะนำ @ alexander-mcfarlane และฟังก์ชั่นทดสอบ


ปัญหาหนึ่งกับแถบความคืบหน้าของคุณ ... แถบจะวัดว่าเวิร์กโหลดนั้นมีประสิทธิภาพเพียงใดในการแยกระหว่างโปรเซสเซอร์ หากเวิร์กโหลดแบ่งอย่างสมบูรณ์โปรเซสเซอร์ทั้งหมดจะjoin()พร้อมกันและคุณจะได้รับแฟลชที่100%เสร็จสมบูรณ์ในtqdmหน้าจอ เวลาเท่านั้นที่จะมีประโยชน์คือถ้าตัวประมวลผลแต่ละตัวมีเวิร์กโหลดจำนวนมาก
Alexander McFarlane

1
ย้ายtqdm()ไปที่บรรทัด: result = [q_out.get() for _ in tqdm(sent)]และมันทำงานได้ดีขึ้นมาก - ความพยายามที่ดี แต่จริงๆขอบคุณนี้ดังนั้น +1
Alexander McFarlane

ขอบคุณสำหรับคำแนะนำนั้นฉันจะลองแล้วอัปเดตคำตอบ!
xApple

คำตอบจะได้รับการอัปเดตและแถบความคืบหน้าจะทำงานได้ดีขึ้นมาก!
xApple

8

ฉันรู้ว่าสิ่งนี้ถูกถามเมื่อ 6 ปีที่แล้วตอนนี้ แต่เพียงต้องการเพิ่มโซลูชันของฉันเนื่องจากคำแนะนำบางอย่างข้างต้นดูเหมือนซับซ้อนอย่างน่ากลัว แต่จริงๆแล้วโซลูชันของฉันง่ายมาก

สิ่งที่ฉันต้องทำก็แค่ใส่การโทร pool.map () เข้ากับฟังก์ชันผู้ช่วย ส่งผ่านคลาสวัตถุพร้อมกับ args เพื่อหาวิธีเป็น tuple ซึ่งมีลักษณะเช่นนี้

def run_in_parallel(args):
    return args[0].method(args[1])

myclass = MyClass()
method_args = [1,2,3,4,5,6]
args_map = [ (myclass, arg) for arg in method_args ]
pool = Pool()
pool.map(run_in_parallel, args_map)

7

ฟังก์ชั่นที่กำหนดไว้ในชั้นเรียน (แม้ในฟังก์ชั่นภายในชั้นเรียน) ไม่ได้ดองจริงๆ อย่างไรก็ตามการทำงานนี้:

def f(x):
    return x*x

class calculate(object):
    def run(self):
        p = Pool()
    return p.map(f, [1,2,3])

cl = calculate()
print cl.run()

15
ขอบคุณ แต่ฉันคิดว่ามันสกปรกเล็กน้อยในการกำหนดฟังก์ชั่นนอกชั้นเรียน ชั้นควรรวมทั้งหมดที่จำเป็นเพื่อให้บรรลุภารกิจที่กำหนด
Mermoz

3
@Memoz: "ชั้นควรรวมทุกอย่างที่ต้องการ" จริงเหรอ? ฉันไม่พบตัวอย่างของสิ่งนี้มากมาย คลาสส่วนใหญ่ขึ้นอยู่กับคลาสหรือฟังก์ชั่นอื่น ๆ เหตุใดจึงต้องเรียกชั้นเรียนว่า "สกปรก" มีอะไรผิดปกติกับการพึ่งพา?
S.Lott

ฟังก์ชั่นไม่ควรแก้ไขข้อมูลคลาสที่มีอยู่ - เพราะมันจะแก้ไขเวอร์ชันในกระบวนการอื่น - ดังนั้นจึงอาจเป็นวิธีการคงที่ คุณสามารถเรียงลำดับของวิธีการคงที่: stackoverflow.com/questions/1914261/ … หรือสำหรับสิ่งเล็กน้อยนี้คุณสามารถใช้แลมบ์ดา
เบิร์ต

6

ฉันรู้ว่าคำถามนี้ถูกถามเมื่อ 8 ปีที่แล้วและ 10 เดือนที่ผ่านมา แต่ฉันต้องการที่จะนำเสนอวิธีการแก้ปัญหาของคุณ:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @staticmethod
    def methodForMultiprocessing(x):
        print(x*x)

    def main(self):
        if __name__ == "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

คุณเพียงแค่ต้องทำให้คลาสของคุณทำงานเป็นวิธีคงที่ แต่มันก็เป็นไปได้ด้วยวิธีการเรียน:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @classmethod
    def methodForMultiprocessing(cls, x):
        print(x*x)

    def main(self):
        if __name__ == "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

ทดสอบใน Python 3.7.3


3

ฉันปรับเปลี่ยนวิธีการของ klaus se เพราะในขณะที่มันทำงานให้ฉันด้วยรายการเล็ก ๆ มันจะแขวนเมื่อจำนวนของรายการเป็น ~ 1,000 หรือมากกว่า แทนที่จะผลักงานหนึ่งครั้งพร้อมNoneเงื่อนไขหยุดทำงานฉันจะโหลดคิวอินพุตทั้งหมดในครั้งเดียวและปล่อยให้กระบวนการเคี้ยวบนมันจนกว่ามันจะว่างเปล่า

from multiprocessing import cpu_count, Queue, Process

def apply_func(f, q_in, q_out):
    while not q_in.empty():
        i, x = q_in.get()
        q_out.put((i, f(x)))

# map a function using a pool of processes
def parmap(f, X, nprocs = cpu_count()):
    q_in, q_out   = Queue(), Queue()
    proc = [Process(target=apply_func, args=(f, q_in, q_out)) for _ in range(nprocs)]
    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [p.start() for p in proc]
    res = [q_out.get() for _ in sent]
    [p.join() for p in proc]

    return [x for i,x in sorted(res)]

แก้ไข: น่าเสียดายที่ตอนนี้ฉันพบข้อผิดพลาดนี้ในระบบของฉัน: การจำกัดการประมวลผลหลายคิวจำนวนสูงสุดคือ 32767หวังว่าการแก้ไขปัญหาจะมีประโยชน์


1

คุณสามารถเรียกใช้รหัสของคุณโดยไม่มีปัญหาใด ๆ หากคุณละเว้นPoolวัตถุด้วยตนเองจากรายการของวัตถุในคลาสเนื่องจากจะไม่pickleสามารถตามที่ระบุข้อผิดพลาด คุณสามารถทำได้ด้วย__getstate__ฟังก์ชั่น (ดูที่นี่ด้วย) ดังต่อไปนี้ Poolวัตถุจะพยายามที่จะหา__getstate__และ__setstate__ฟังก์ชั่นและรันพวกเขาหากพบมันเมื่อคุณทำงานmap, map_asyncฯลฯ :

class calculate(object):
    def __init__(self):
        self.p = Pool()
    def __getstate__(self):
        self_dict = self.__dict__.copy()
        del self_dict['p']
        return self_dict
    def __setstate__(self, state):
        self.__dict__.update(state)

    def f(self, x):
        return x*x
    def run(self):
        return self.p.map(self.f, [1,2,3])

จากนั้นทำ:

cl = calculate()
cl.run()

จะให้ผลลัพธ์:

[1, 4, 9]

ฉันทดสอบโค้ดข้างต้นใน Python 3.x แล้วและยังใช้งานได้


0

ฉันไม่แน่ใจว่าวิธีนี้ได้รับการดำเนินการ แต่การทำงานรอบ ๆ ฉันกำลังใช้คือ:

from multiprocessing import Pool

t = None

def run(n):
    return t.f(n)

class Test(object):
    def __init__(self, number):
        self.number = number

    def f(self, x):
        print x * self.number

    def pool(self):
        pool = Pool(2)
        pool.map(run, range(10))

if __name__ == '__main__':
    t = Test(9)
    t.pool()
    pool = Pool(2)
    pool.map(run, range(10))

ผลลัพธ์ควรเป็น:

0
9
18
27
36
45
54
63
72
81
0
9
18
27
36
45
54
63
72
81

0
class Calculate(object):
  # Your instance method to be executed
  def f(self, x, y):
    return x*y

if __name__ == '__main__':
  inp_list = [1,2,3]
  y = 2
  cal_obj = Calculate()
  pool = Pool(2)
  results = pool.map(lambda x: cal_obj.f(x, y), inp_list)

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

class Calculate(object):
  # Your instance method to be executed
  def __init__(self, x):
    self.x = x

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

if __name__ == '__main__':
  inp_list = [Calculate(i) for i in range(3)]
  y = 2
  pool = Pool(2)
  results = pool.map(lambda x: x.f(y), inp_list)

0

นี่คือทางออกของฉันซึ่งฉันคิดว่ามันแฮ็คน้อยกว่าคนอื่น ๆ ที่นี่ มันคล้ายกับคำตอบของ nightowl

someclasses = [MyClass(), MyClass(), MyClass()]

def method_caller(some_object, some_method='the method'):
    return getattr(some_object, some_method)()

othermethod = partial(method_caller, some_method='othermethod')

with Pool(6) as pool:
    result = pool.map(othermethod, someclasses)

0

จากhttp://www.rueckstiess.net/research/snippets/show/ca1d7d90และ http://qingkaikong.blogspot.com/2016/12/python-parallel-method-in-class.html

เราสามารถสร้างฟังก์ชั่นภายนอกและเมล็ดด้วยวัตถุตัวเองระดับ:

from joblib import Parallel, delayed
def unwrap_self(arg, **kwarg):
    return square_class.square_int(*arg, **kwarg)

class square_class:
    def square_int(self, i):
        return i * i

    def run(self, num):
        results = []
        results = Parallel(n_jobs= -1, backend="threading")\
            (delayed(unwrap_self)(i) for i in zip([self]*len(num), num))
        print(results)

หรือไม่มี joblib:

from multiprocessing import Pool
import time

def unwrap_self_f(arg, **kwarg):
    return C.f(*arg, **kwarg)

class C:
    def f(self, name):
        print 'hello %s,'%name
        time.sleep(5)
        print 'nice to meet you.'

    def run(self):
        pool = Pool(processes=2)
        names = ('frank', 'justin', 'osi', 'thomas')
        pool.map(unwrap_self_f, zip([self]*len(names), names))

if __name__ == '__main__':
    c = C()
    c.run()

0

นี่อาจไม่ใช่วิธีที่ดีมาก แต่ในกรณีของฉันฉันแก้มันแบบนี้

from multiprocessing import Pool

def foo1(data):
    self = data.get('slf')
    lst = data.get('lst')
    return sum(lst) + self.foo2()

class Foo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def foo2(self):
        return self.a**self.b   

    def foo(self):
        p = Pool(5)
        lst = [1, 2, 3]
        result = p.map(foo1, (dict(slf=self, lst=lst),))
        return result

if __name__ == '__main__':
    print(Foo(2, 4).foo())

ฉันต้องผ่านselfไปยังฟังก์ชั่นของฉันเพราะฉันต้องเข้าถึงคุณลักษณะและฟังก์ชั่นในชั้นเรียนของฉันผ่านฟังก์ชั่นนั้น มันใช้งานได้สำหรับฉัน ยินดีต้อนรับการแก้ไขและข้อเสนอแนะ


0

นี่คือต้นแบบที่ฉันเขียนขึ้นสำหรับการใช้มัลติโพรเซสเซอร์ใน python3 โดยเฉพาะ python3.7.7 ถูกใช้เพื่อเรียกใช้การทดสอบ imap_unorderedผมได้วิ่งที่เร็วที่สุดของฉันโดยใช้ เพียงเสียบสถานการณ์ของคุณและลองใช้งาน คุณสามารถใช้timeitหรือtime.time()คิดออกว่าอะไรดีที่สุดสำหรับคุณ

import multiprocessing
import time

NUMBER_OF_PROCESSES = multiprocessing.cpu_count()
MP_FUNCTION = 'starmap'  # 'imap_unordered' or 'starmap' or 'apply_async'

def process_chunk(a_chunk):
    print(f"processig mp chunk {a_chunk}")
    return a_chunk


map_jobs = [1, 2, 3, 4]

result_sum = 0

s = time.time()
if MP_FUNCTION == 'imap_unordered':
    pool = multiprocessing.Pool(processes=NUMBER_OF_PROCESSES)
    for i in pool.imap_unordered(process_chunk, map_jobs):
        result_sum += i
elif MP_FUNCTION == 'starmap':
    pool = multiprocessing.Pool(processes=NUMBER_OF_PROCESSES)
    try:
        map_jobs = [(i, ) for i in map_jobs]
        result_sum = pool.starmap(process_chunk, map_jobs)
        result_sum = sum(result_sum)
    finally:
        pool.close()
        pool.join()
elif MP_FUNCTION == 'apply_async':
    with multiprocessing.Pool(processes=NUMBER_OF_PROCESSES) as pool:
        result_sum = [pool.apply_async(process_chunk, [i, ]).get() for i in map_jobs]
    result_sum = sum(result_sum)
print(f"result_sum is {result_sum}, took {time.time() - s}s")

ในสถานการณ์ข้างต้นimap_unorderedจริง ๆ แล้วดูเหมือนว่าแย่ที่สุดสำหรับฉัน ลองใช้เคสของคุณและเปรียบเทียบกับเครื่องที่คุณวางแผนจะรัน นอกจากนี้ยังอ่านข้อมูลเกี่ยวกับสระว่ายน้ำในกระบวนการ ไชโย!

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