ฉันจะกู้คืนค่าส่งคืนของฟังก์ชันที่ส่งผ่านไปยังกระบวนการหลายตัวประมวลผลได้อย่างไร


190

workerในรหัสตัวอย่างด้านล่างฉันต้องการที่จะกู้คืนค่าตอบแทนของฟังก์ชั่น ฉันจะทำสิ่งนี้ได้อย่างไร ค่านี้ถูกเก็บไว้ที่ไหน?

รหัสตัวอย่าง:

import multiprocessing

def worker(procnum):
    '''worker function'''
    print str(procnum) + ' represent!'
    return procnum


if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print jobs

เอาท์พุท:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[<Process(Process-1, stopped)>, <Process(Process-2, stopped)>, <Process(Process-3, stopped)>, <Process(Process-4, stopped)>, <Process(Process-5, stopped)>]

ฉันไม่พบคุณลักษณะที่เกี่ยวข้องในวัตถุที่เก็บไว้ jobsฉันไม่สามารถดูเหมือนจะหาแอตทริบิวต์ที่เกี่ยวข้องในวัตถุที่เก็บไว้ใน

คำตอบ:


189

ใช้ตัวแปรที่แชร์เพื่อสื่อสาร ตัวอย่างเช่นนี้:

import multiprocessing

def worker(procnum, return_dict):
    '''worker function'''
    print str(procnum) + ' represent!'
    return_dict[procnum] = procnum


if __name__ == '__main__':
    manager = multiprocessing.Manager()
    return_dict = manager.dict()
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,return_dict))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print return_dict.values()

46
ฉันอยากจะแนะนำให้ใช้ a multiprocessing.Queueมากกว่าManagerที่นี่ การใช้Managerต้องมีการวางไข่กระบวนการใหม่ทั้งหมดซึ่งเกินขีดเมื่อมีการQueueทำ
dano

1
@dano: ฉันสงสัยว่าถ้าเราใช้วัตถุ Queue () เราไม่สามารถมั่นใจได้ว่าคำสั่งซื้อเมื่อแต่ละกระบวนการคืนค่า ฉันหมายถึงถ้าเราต้องการคำสั่งในผลลัพธ์เพื่อทำงานต่อไป เราจะแน่ใจได้อย่างไรว่าผลลัพธ์นั้นมาจากกระบวนการใด
Catbuilts

4
@Catbuilts คุณสามารถส่งคืน tuple จากแต่ละกระบวนการโดยที่หนึ่งค่าคือมูลค่าส่งคืนจริงที่คุณสนใจและอีกรายการหนึ่งเป็นตัวระบุที่ไม่ซ้ำกันจากกระบวนการ แต่ฉันก็ยังสงสัยว่าทำไมคุณต้องรู้ว่ากระบวนการใดจะคืนค่าใด ถ้านั่นคือสิ่งที่คุณต้องรู้เกี่ยวกับกระบวนการจริงหรือคุณต้องการเชื่อมโยงระหว่างรายการอินพุตกับรายการผลลัพธ์หรือไม่ ในกรณีนี้ฉันขอแนะนำให้ใช้multiprocessing.Pool.mapในการประมวลผลรายการงานของคุณ
dano

5
คำเตือนสำหรับฟังก์ชั่นที่มีเพียงอาร์กิวเมนต์เดียว : args=(my_function_argument, )ควรใช้ สังเกต,เครื่องหมายจุลภาคที่นี่! มิฉะนั้น Python จะบ่นว่า "ไม่มีข้อโต้แย้งตำแหน่ง" พาฉันไป 10 นาทีเพื่อหา ตรวจสอบการใช้งานด้วยตนเอง (ในส่วน "ระดับกระบวนการ")
yuqli

2
@vartec หนึ่งข้อเสียเปรียบของการใช้พจนานุกรม multipriocessing.Manager () คือนั่นคือ pickles (serializes) วัตถุมันส่งกลับดังนั้นจึงมีคอขวดที่กำหนดโดยห้องสมุด pickle ขนาด 2GiB สูงสุดสำหรับวัตถุที่จะกลับมา มีวิธีอื่นในการทำเช่นนี้เพื่อหลีกเลี่ยงการซีเรียลไลซ์เซชันของวัตถุที่ส่งคืนหรือไม่
hirschme

68

ฉันคิดว่าวิธีที่แนะนำโดย @sega_sai นั้นดีกว่า แต่มันต้องการตัวอย่างโค้ดจริงๆแล้วต่อไปนี้:

import multiprocessing
from os import getpid

def worker(procnum):
    print('I am number %d in process %d' % (procnum, getpid()))
    return getpid()

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes = 3)
    print(pool.map(worker, range(5)))

ซึ่งจะพิมพ์ค่าตอบแทน:

I am number 0 in process 19139
I am number 1 in process 19138
I am number 2 in process 19140
I am number 3 in process 19139
I am number 4 in process 19140
[19139, 19138, 19140, 19139, 19140]

หากคุณคุ้นเคยกับmap(ตัว Python 2) สิ่งนี้ไม่ควรท้าทายเกินไป มิฉะนั้นมีลักษณะที่เชื่อมโยง sega_Sai ของ

โปรดทราบว่าจำเป็นต้องใช้รหัสน้อยเพียงใด (โปรดทราบว่าจะใช้กระบวนการได้อย่างไร)


1
ความคิดใด ๆ ที่ทำให้ฉันgetpid()กลับมามีค่าเท่ากัน? ฉันใช้ Python3
zelusp

ฉันไม่แน่ใจว่า Pool กระจายงานให้คนงานอย่างไร บางทีพวกเขาอาจจะจบลงที่คนงานเดียวกันหากพวกเขาเร็วจริง ๆ ? มันเกิดขึ้นอย่างสม่ำเสมอหรือไม่? นอกจากนี้หากคุณเพิ่มความล่าช้าหรือไม่
ทำเครื่องหมาย

ฉันยังคิดว่ามันเป็นสิ่งที่เกี่ยวข้องกับความเร็ว แต่เมื่อฉันป้อนpool.mapช่วง 1,000,000 โดยใช้มากกว่า 10 กระบวนการฉันเห็นสอง pids ที่แตกต่างกันมากที่สุด
zelusp

1
ถ้าอย่างนั้นฉันก็ไม่แน่ใจ ฉันคิดว่ามันน่าสนใจที่จะเปิดคำถามแยกต่างหากสำหรับเรื่องนี้
ทำเครื่องหมาย

หากสิ่งที่คุณต้องการส่งฟังก์ชั่นที่แตกต่างกันไปในแต่ละกระบวนการใช้pool.apply_async: docs.python.org/3/library/…
Kyle

24

ตัวอย่างนี้แสดงวิธีใช้รายการอินสแตนซ์multiprocessing.Pipeเพื่อส่งคืนสตริงจากจำนวนกระบวนการโดยพลการ:

import multiprocessing

def worker(procnum, send_end):
    '''worker function'''
    result = str(procnum) + ' represent!'
    print result
    send_end.send(result)

def main():
    jobs = []
    pipe_list = []
    for i in range(5):
        recv_end, send_end = multiprocessing.Pipe(False)
        p = multiprocessing.Process(target=worker, args=(i, send_end))
        jobs.append(p)
        pipe_list.append(recv_end)
        p.start()

    for proc in jobs:
        proc.join()
    result_list = [x.recv() for x in pipe_list]
    print result_list

if __name__ == '__main__':
    main()

เอาท์พุท:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
['0 represent!', '1 represent!', '2 represent!', '3 represent!', '4 represent!']

วิธีการแก้ปัญหานี้ใช้ทรัพยากรน้อยกว่าการประมวลผลมัลติคิวที่ใช้

  • ท่อ
  • ล็อคอย่างน้อยหนึ่งตัว
  • บัฟเฟอร์
  • ด้าย

หรือการประมวลผลแบบมัลติ SimpleQueueซึ่งใช้

  • ท่อ
  • ล็อคอย่างน้อยหนึ่งตัว

เป็นคำแนะนำที่ดีในการดูแหล่งที่มาสำหรับแต่ละประเภทเหล่านี้


อะไรจะเป็นวิธีที่ดีที่สุดในการทำเช่นนั้นโดยไม่ทำให้ท่อกลายเป็นตัวแปรส่วนกลาง
Nickpick

ฉันใส่ข้อมูลทั่วโลกและรหัสลงในฟังก์ชั่นหลักและทำงานเหมือนกัน นั่นตอบคำถามของคุณหรือไม่
David Cullen

ท่อจะต้องอ่านก่อนที่จะมีการเพิ่มค่าใหม่ (ส่ง) ให้กับมันหรือไม่?
Nickpick

+1 คำตอบที่ดี แต่เกี่ยวกับการแก้ปัญหาที่มีประสิทธิภาพมากขึ้นข้อเสียคือคุณต้องทำอย่างใดอย่างหนึ่งPipeต่อกระบวนการเทียบกับวิธีหนึ่งQueueสำหรับกระบวนการทั้งหมด ฉันไม่รู้ว่าสิ่งนั้นจะมีประสิทธิภาพมากกว่าในทุกกรณีหรือไม่
sudo

2
คำตอบนี้ทำให้เกิดการหยุดชะงักหากวัตถุที่ส่งคืนมีขนาดใหญ่ แทนที่จะทำ proc.join () ก่อนฉันจะลอง recv () ค่าส่งคืนก่อนแล้วจึงเข้าร่วม
L. Pes

22

ด้วยเหตุผลบางอย่างฉันไม่สามารถหาตัวอย่างทั่วไปของวิธีการทำสิ่งนี้Queueได้ทุกที่ (แม้แต่ตัวอย่างเอกสารของ Python ก็ไม่ได้วางไข่หลายขั้นตอน) ดังนั้นนี่คือสิ่งที่ฉันได้ทำงานหลังจากพยายาม 10 ครั้ง:

def add_helper(queue, arg1, arg2): # the func called in child processes
    ret = arg1 + arg2
    queue.put(ret)

def multi_add(): # spawns child processes
    q = Queue()
    processes = []
    rets = []
    for _ in range(0, 100):
        p = Process(target=add_helper, args=(q, 1, 2))
        processes.append(p)
        p.start()
    for p in processes:
        ret = q.get() # will block
        rets.append(ret)
    for p in processes:
        p.join()
    return rets

Queueคือคิวการบล็อกเธรดที่ปลอดภัยที่คุณสามารถใช้เพื่อเก็บค่าส่งคืนจากกระบวนการลูก ดังนั้นคุณต้องส่งคิวไปยังแต่ละกระบวนการ สิ่งที่เห็นได้ชัดน้อยกว่านี้คือคุณต้องget()ออกจากคิวก่อนที่คุณจะjoinรับชมProcessมิฉะนั้นคิวจะเต็มและบล็อกทุกอย่าง

อัปเดตสำหรับผู้ที่เน้นวัตถุ (ทดสอบใน Python 3.4):

from multiprocessing import Process, Queue

class Multiprocessor():

    def __init__(self):
        self.processes = []
        self.queue = Queue()

    @staticmethod
    def _wrapper(func, queue, args, kwargs):
        ret = func(*args, **kwargs)
        queue.put(ret)

    def run(self, func, *args, **kwargs):
        args2 = [func, self.queue, args, kwargs]
        p = Process(target=self._wrapper, args=args2)
        self.processes.append(p)
        p.start()

    def wait(self):
        rets = []
        for p in self.processes:
            ret = self.queue.get()
            rets.append(ret)
        for p in self.processes:
            p.join()
        return rets

# tester
if __name__ == "__main__":
    mp = Multiprocessor()
    num_proc = 64
    for _ in range(num_proc): # queue up multiple tasks running `sum`
        mp.run(sum, [1, 2, 3, 4, 5])
    ret = mp.wait() # get all results
    print(ret)
    assert len(ret) == num_proc and all(r == 15 for r in ret)

18

สำหรับคนอื่น ๆ ที่กำลังมองหาวิธีการรับค่าจากการProcessใช้Queue:

import multiprocessing

ret = {'foo': False}

def worker(queue):
    ret = queue.get()
    ret['foo'] = True
    queue.put(ret)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    queue.put(ret)
    p = multiprocessing.Process(target=worker, args=(queue,))
    p.start()
    print queue.get()  # Prints {"foo": True}
    p.join()

1
เมื่อฉันใส่อะไรลงในคิวในกระบวนการทำงานของฉันการเข้าร่วมของฉันจะไม่ถึง ความคิดใด ๆ ที่อาจเกิดขึ้นได้
Laurens Koppenol

@LaurensKoppenol คุณหมายความว่ารหัสหลักของคุณแฮงค์ที่ p.join () อย่างถาวรและไม่ดำเนินการต่อหรือไม่ กระบวนการของคุณมีการวนซ้ำไม่สิ้นสุดหรือไม่?
Matthew Moisen

4
ใช่มันแขวนอยู่ที่นั่นอย่างไม่สิ้นสุด พนักงานของฉันเสร็จสิ้นทั้งหมด (วนซ้ำภายในฟังก์ชันของผู้ปฏิบัติงานสิ้นสุดคำสั่งพิมพ์หลังจากนั้นจะถูกพิมพ์สำหรับพนักงานทุกคน) การเข้าร่วมไม่ได้ทำอะไรเลย ถ้าฉันลบออกQueueจากฟังก์ชั่นของฉันมันจะให้ฉันผ่านjoin()
Laurens Koppenol

@LaurensKoppenol คุณอาจไม่ได้โทรมาqueue.put(ret)ก่อนการโทรp.start()หรือเปล่า? ในกรณีนั้นเธรดผู้ปฏิบัติงานจะแขวนqueue.get()ตลอดไป queue.put(ret)คุณสามารถทำซ้ำนี้โดยการคัดลอกข้อมูลโค้ดของฉันข้างต้นในขณะที่แสดงความคิดเห็นออก
Matthew Moisen

ฉันแก้ไขคำตอบนี้มีที่จะเกิดขึ้นก่อนqueue.get() p.join()มันใช้งานได้แล้วสำหรับฉัน
jfunk

12

ดูเหมือนว่าคุณควรใช้คลาสmultiprocessing.Poolแทนและใช้เมธอด .apply () .apply_async (), map ()

http://docs.python.org/library/multiprocessing.html?highlight=pool#multiprocessing.pool.AsyncResult


ฉันมีรหัส tensorflow ซึ่งการประมวลผลแบบมัลติพูลจะหยุดทำงาน แต่ไม่ประมวลผลหลายตัวประมวลผล
Le Frite

10

คุณสามารถใช้exitบิวด์อินเพื่อตั้งค่ารหัสการออกของกระบวนการ สามารถรับได้จากexitcodeคุณสมบัติของกระบวนการ:

import multiprocessing

def worker(procnum):
    print str(procnum) + ' represent!'
    exit(procnum)

if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    result = []
    for proc in jobs:
        proc.join()
        result.append(proc.exitcode)
    print result

เอาท์พุท:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

4
ถูกเตือนว่าวิธีการนี้อาจทำให้เกิดความสับสน กระบวนการโดยทั่วไปควรจบการทำงานด้วยรหัสการออก 0 จะเสร็จสมบูรณ์โดยไม่มีข้อผิดพลาด หากคุณมีสิ่งใดที่ตรวจสอบรหัสออกจากกระบวนการของระบบคุณอาจเห็นว่าการรายงานเหล่านี้เป็นข้อผิดพลาด
ferrouswheel

1
สมบูรณ์แบบถ้าคุณเพียงต้องการเพิ่มข้อยกเว้นในกระบวนการหลักเมื่อเกิดข้อผิดพลาด
crizCraig

5

กรวดแพคเกจที่มีการใช้ประโยชน์จากสิ่งที่เป็นนามธรรมที่ดีmultiprocessing.Pipeซึ่งจะทำให้นี้ตรงไปตรงมามาก:

from pebble import concurrent

@concurrent.process
def function(arg, kwarg=0):
    return arg + kwarg

future = function(1, kwarg=1)

print(future.result())

ตัวอย่างจาก: https://pythonhosted.org/Pebble/#concurrent-decorators


3

คิดว่าฉันจะลดความซับซ้อนของตัวอย่างที่ง่ายที่สุดที่คัดลอกมาจากด้านบนทำงานให้ฉันบน Py3.6 ที่ง่ายที่สุดคือmultiprocessing.Pool:

import multiprocessing
import time

def worker(x):
    time.sleep(1)
    return x

pool = multiprocessing.Pool()
print(pool.map(worker, range(10)))

Pool(processes=5)คุณสามารถตั้งค่าจำนวนของกระบวนการในสระว่ายน้ำด้วยเช่น อย่างไรก็ตามมันจะมีค่าเริ่มต้นที่จะนับ CPU ดังนั้นปล่อยว่างไว้สำหรับงานที่ผูกกับ CPU (ภารกิจ I / O-bound มักจะเหมาะกับเธรดอยู่แล้วเนื่องจากเธรดส่วนใหญ่กำลังรออยู่จึงสามารถแชร์ซีพียูคอร์ได้) Poolนอกจากนี้ยังใช้การเพิ่มประสิทธิภาพแบบ chunkingการเพิ่มประสิทธิภาพ

(โปรดทราบว่าวิธีการของผู้ปฏิบัติงานไม่สามารถซ้อนกันภายในวิธีการฉันเริ่มต้นกำหนดวิธีการทำงานของฉันภายในวิธีที่ทำให้การเรียกpool.mapเพื่อให้มันทั้งหมดในตัวเอง แต่แล้วกระบวนการไม่สามารถนำเข้าและโยน "AttributeError : ไม่สามารถเลือกวัตถุท้องถิ่น outer_method ..inner_method "ได้ที่นี่เพิ่มเติม . มันสามารถอยู่ในชั้นเรียนได้)

(ขอบคุณคำถามต้นฉบับที่ระบุการพิมพ์'represent!'มากกว่าtime.sleep()แต่ไม่คิดว่าฉันคิดว่ารหัสบางอย่างกำลังทำงานพร้อมกันเมื่อมันไม่ได้)


Py3's ProcessPoolExecutorเป็นสองบรรทัด ( .mapคืนค่าตัวสร้างดังนั้นคุณต้องการlist()):

from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as executor:
    print(list(executor.map(worker, range(10))))

ด้วยProcesses ธรรมดา:

import multiprocessing
import time

def worker(x, queue):
    time.sleep(1)
    queue.put(x)

queue = multiprocessing.SimpleQueue()
tasks = range(10)

for task in tasks:
    multiprocessing.Process(target=worker, args=(task, queue,)).start()

for _ in tasks:
    print(queue.get())

ใช้SimpleQueueถ้าสิ่งที่คุณต้องการคือและput getวนแรกเริ่มกระบวนการทั้งหมดก่อนที่สองทำการqueue.getโทรบล็อก ฉันไม่คิดว่าจะมีเหตุผลอะไรที่ต้องโทรหาp.join()ด้วย


2

ทางออกที่ง่าย:

import multiprocessing

output=[]
data = range(0,10)

def f(x):
    return x**2

def handler():
    p = multiprocessing.Pool(64)
    r=p.map(f, data)
    return r

if __name__ == '__main__':
    output.append(handler())

print(output[0])

เอาท์พุท:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

2

หากคุณใช้ Python 3 คุณสามารถใช้concurrent.futures.ProcessPoolExecutorเป็นนามธรรมได้อย่างสะดวก:

from concurrent.futures import ProcessPoolExecutor

def worker(procnum):
    '''worker function'''
    print(str(procnum) + ' represent!')
    return procnum


if __name__ == '__main__':
    with ProcessPoolExecutor() as executor:
        print(list(executor.map(worker, range(5))))

เอาท์พุท:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

0

ฉันแก้ไขคำตอบของ vartec เล็กน้อยเนื่องจากฉันต้องการรับรหัสข้อผิดพลาดจากฟังก์ชัน (ขอบคุณจุดสุดยอด !!! มันเป็นเคล็ดลับที่ยอดเยี่ยม)

สิ่งนี้สามารถทำได้ด้วยmanager.listแต่ฉันคิดว่าดีกว่าที่จะมีใน dict และเก็บรายการไว้ภายใน ด้วยวิธีนี้วิธีที่เราใช้รักษาฟังก์ชั่นและผลลัพธ์เนื่องจากเราไม่แน่ใจว่าจะเรียงลำดับรายการได้อย่างไร

from multiprocessing import Process
import time
import datetime
import multiprocessing


def func1(fn, m_list):
    print 'func1: starting'
    time.sleep(1)
    m_list[fn] = "this is the first function"
    print 'func1: finishing'
    # return "func1"  # no need for return since Multiprocess doesnt return it =(

def func2(fn, m_list):
    print 'func2: starting'
    time.sleep(3)
    m_list[fn] = "this is function 2"
    print 'func2: finishing'
    # return "func2"

def func3(fn, m_list):
    print 'func3: starting'
    time.sleep(9)
    # if fail wont join the rest because it never populate the dict
    # or do a try/except to get something in return.
    raise ValueError("failed here")
    # if we want to get the error in the manager dict we can catch the error
    try:
        raise ValueError("failed here")
        m_list[fn] = "this is third"
    except:
        m_list[fn] = "this is third and it fail horrible"
        # print 'func3: finishing'
        # return "func3"


def runInParallel(*fns):  # * is to accept any input in list
    start_time = datetime.datetime.now()
    proc = []
    manager = multiprocessing.Manager()
    m_list = manager.dict()
    for fn in fns:
        # print fn
        # print dir(fn)
        p = Process(target=fn, name=fn.func_name, args=(fn, m_list))
        p.start()
        proc.append(p)
    for p in proc:
        p.join()  # 5 is the time out

    print datetime.datetime.now() - start_time
    return m_list, proc

if __name__ == '__main__':
    manager, proc = runInParallel(func1, func2, func3)
    # print dir(proc[0])
    # print proc[0]._name
    # print proc[0].name
    # print proc[0].exitcode

    # here you can check what did fail
    for i in proc:
        print i.name, i.exitcode  # name was set up in the Process line 53

    # here will only show the function that worked and where able to populate the 
    # manager dict
    for i, j in manager.items():
        print dir(i)  # things you can do to the function
        print i, j
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.