Làm cách nào tôi có thể khôi phục giá trị trả về của hàm được truyền cho đa xử lý. Xử lý?


188

Trong mã ví dụ bên dưới, tôi muốn khôi phục giá trị trả về của hàm worker. Làm thế nào tôi có thể đi về làm điều này? Giá trị này được lưu trữ ở đâu?

Mã ví dụ:

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

Đầu ra:

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

Tôi dường như không thể tìm thấy thuộc tính có liên quan trong các đối tượng được lưu trữ trong jobs.

Câu trả lời:


187

Sử dụng biến chia sẻ để giao tiếp. Ví dụ như thế này:

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
Tôi sẽ khuyên bạn nên sử dụng một multiprocessing.Queue, chứ không phải là Managerở đây. Sử dụng một Manageryêu cầu sinh ra một quy trình hoàn toàn mới, đó là quá mức cần thiết khi Queuelàm.
dano

1
@dano: Tôi tự hỏi, nếu chúng tôi sử dụng đối tượng Queue (), chúng tôi không thể chắc chắn thứ tự khi mỗi quá trình trả về giá trị. Ý tôi là nếu chúng ta cần thứ tự trong kết quả, để thực hiện công việc tiếp theo. Làm thế nào chúng ta có thể chắc chắn chính xác đầu ra từ quá trình nào
Catbuilts

4
@Catbuilts Bạn có thể trả về một tuple từ mỗi quy trình, trong đó một giá trị là giá trị trả về thực tế mà bạn quan tâm và giá trị còn lại là một định danh duy nhất từ ​​quy trình. Nhưng tôi cũng tự hỏi tại sao bạn cần biết quá trình nào đang trả về giá trị nào. Nếu đó là những gì bạn thực sự cần biết về quy trình, hoặc bạn cần phải tương quan giữa danh sách đầu vào của bạn và danh sách đầu ra? Trong trường hợp đó, tôi khuyên bạn nên sử dụng multiprocessing.Pool.mapđể xử lý danh sách các mục công việc của bạn.
dano

4
hãy cẩn thận cho các hàm chỉ với một đối số duy nhất : nên sử dụng args=(my_function_argument, ). Lưu ý ,dấu phẩy ở đây! Hoặc nếu không Python sẽ phàn nàn "thiếu đối số vị trí". Mất 10 phút để tìm hiểu. Đồng thời kiểm tra việc sử dụng thủ công (trong phần "lớp quy trình").
yuqli

2
@vartec một nhược điểm của việc sử dụng từ điển đa biến.Manager () là dưa chua (tuần tự hóa) đối tượng mà nó trả về, do đó, nó có một nút cổ chai do thư viện dưa chọn có kích thước tối đa 2GiB cho đối tượng trả về. Có cách nào khác để làm điều này tránh việc tuần tự hóa đối tượng quay trở lại không?
hirschme

67

Tôi nghĩ rằng cách tiếp cận được đề xuất bởi @sega_sai là cách tốt hơn. Nhưng nó thực sự cần một ví dụ mã, vì vậy ở đây đi:

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

Sẽ in các giá trị trả về:

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]

Nếu bạn đã quen thuộc với map(Python 2 tích hợp) thì điều này không quá khó khăn. Nếu không, hãy xem liên kết của sega_Sai .

Lưu ý làm thế nào ít mã là cần thiết. (Cũng lưu ý cách các quy trình được sử dụng lại).


1
Bất kỳ ý tưởng tại sao getpid()trả lại của tôi tất cả cùng một giá trị? Tôi đang chạy Python3
zelusp

Tôi không chắc chắn cách Pool phân phối nhiệm vụ cho công nhân. Có lẽ tất cả họ có thể kết thúc tại cùng một công nhân nếu họ thực sự nhanh? Nó xảy ra nhất quán? Ngoài ra nếu bạn thêm một sự chậm trễ?
Đánh dấu

Tôi cũng nghĩ rằng đó là một thứ liên quan đến tốc độ nhưng khi tôi cung cấp pool.mapphạm vi 1.000.000 bằng cách sử dụng hơn 10 quy trình, tôi thấy nhiều nhất là hai giá trị khác nhau.
zelusp

1
Vậy thì tôi không chắc. Tôi nghĩ thật thú vị khi mở một câu hỏi riêng cho việc này.
Đánh dấu

Nếu những thứ bạn muốn gửi một chức năng khác nhau cho mỗi quy trình, hãy sử dụng pool.apply_async: docs.python.org/3/l Library / Kẻ
Kyle

24

Ví dụ này cho thấy cách sử dụng danh sách các trường hợp đa xử lý.Pipe để trả về các chuỗi từ một số quy trình tùy ý:

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

Đầu ra:

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

Giải pháp này sử dụng ít tài nguyên hơn so với đa xử lý . Giá trị sử dụng

  • một ống
  • ít nhất một Khóa
  • Một bộ đệm
  • một chủ đề

hoặc đa xử lý.SimpleQueue sử dụng

  • một ống
  • ít nhất một Khóa

Nó là rất hướng dẫn để xem xét nguồn cho từng loại.


Điều gì sẽ là cách tốt nhất để làm điều đó mà không làm cho các đường ống trở thành một biến toàn cầu?
Nickpick

Tôi đặt tất cả dữ liệu và mã toàn cầu vào một chức năng chính và nó hoạt động như nhau. Điều đó có trả lời câu hỏi của bạn không?
David Cullen

đường ống luôn phải được đọc trước khi bất kỳ giá trị mới nào có thể được thêm (gửi) vào nó?
Nickpick

+1, câu trả lời tốt. Nhưng về giải pháp hiệu quả hơn, sự đánh đổi là bạn đang thực hiện một Pipecho mỗi quy trình so với một Queuecho tất cả các quy trình. Tôi không biết nếu điều đó kết thúc hiệu quả hơn trong mọi trường hợp.
sudo

2
Câu trả lời này gây ra bế tắc nếu đối tượng trả về lớn. Thay vì thực hiện Proc.join () trước tiên tôi sẽ thử recv () giá trị trả về và sau đó thực hiện phép nối.
L. Pes

21

Vì một số lý do, tôi không thể tìm thấy một ví dụ chung về cách thực hiện việc này với Queuebất kỳ nơi nào (ngay cả các ví dụ tài liệu của Python không sinh ra nhiều quy trình), vì vậy đây là những gì tôi đã làm việc sau 10 lần thử:

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

Queuelà một hàng đợi chặn, an toàn luồng mà bạn có thể sử dụng để lưu trữ các giá trị trả về từ các tiến trình con. Vì vậy, bạn phải vượt qua hàng đợi cho mỗi quá trình. Một cái gì đó ít rõ ràng ở đây là bạn phải get()từ hàng đợi trước khi bạn joinsự Processes hoặc khác hàng đợi đầy và khối tất cả mọi thứ.

Cập nhật cho những người hướng đối tượng (được thử nghiệm trong 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

Đối với bất kỳ ai khác đang tìm cách nhận được giá trị từ Processviệc sử dụng 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
Khi tôi đặt một cái gì đó vào hàng đợi trong quy trình công nhân của tôi, sự tham gia của tôi không bao giờ đạt được. Bất cứ ý tưởng làm thế nào điều này có thể đến?
Laurens Koppenol

@LaurensKoppenol có nghĩa là mã chính của bạn bị treo ở p.join () vĩnh viễn và không bao giờ tiếp tục? Quá trình của bạn có một vòng lặp vô hạn?
Matthew Moisen

4
Vâng, nó treo ở đó vô tận. Tất cả công nhân của tôi đều kết thúc (vòng lặp trong chức năng worker kết thúc, in câu lệnh sau đó được in, cho tất cả công nhân). Việc tham gia không làm gì cả. Nếu tôi loại bỏ Queuechức năng của mình, nó sẽ cho phép tôi vượt quajoin()
Laurens Koppenol

@LaurensKoppenol Có lẽ bạn không gọi queue.put(ret)trước khi gọi p.start()? Trong trường hợp đó, luồng công nhân sẽ treo ở queue.get()mãi mãi. Bạn có thể sao chép điều này bằng cách sao chép đoạn trích của tôi ở trên trong khi bình luận queue.put(ret).
Matthew Moisen

Tôi chỉnh sửa câu trả lời này, queue.get()phải xảy ra trước p.join(). Nó hoạt động bây giờ cho tôi.
jfunk


10

Bạn có thể sử dụng tích exithợp để đặt mã thoát của một quy trình. Nó có thể được lấy từ exitcodethuộc tính của quá trình:

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

Đầu ra:

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

4
Được cảnh báo rằng phương pháp này có thể trở nên khó hiểu. Các quy trình thường thoát với mã thoát 0 là chúng đã hoàn thành không có lỗi. Nếu bạn có bất cứ điều gì giám sát mã thoát quy trình hệ thống của bạn thì bạn có thể thấy những lỗi này được báo cáo là lỗi.
Ferrouswheel

1
Hoàn hảo nếu bạn chỉ muốn đưa ra một ngoại lệ trong quá trình cha mẹ bị lỗi.
crizCraig


3

Tôi nghĩ tôi sẽ đơn giản hóa các ví dụ đơn giản nhất được sao chép từ trên xuống, làm việc cho tôi trên Py3.6. Đơn giản nhất là multiprocessing.Pool:

import multiprocessing
import time

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

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

Bạn có thể đặt số lượng quy trình trong nhóm với, ví dụ : Pool(processes=5). Tuy nhiên, nó mặc định là số lượng CPU, vì vậy hãy để trống cho các tác vụ gắn với CPU. (Dù sao các tác vụ bị ràng buộc I / O thường phù hợp với các luồng, vì các luồng chủ yếu đang chờ nên có thể chia sẻ lõi CPU.) PoolCũng áp dụng tối ưu hóa chunking .

(Lưu ý rằng phương thức worker không thể được lồng trong một phương thức. Ban đầu tôi đã định nghĩa phương thức worker của mình bên trong phương thức thực hiện cuộc gọi đến pool.map, để giữ cho nó hoàn toàn khép kín, nhưng sau đó các quy trình không thể nhập nó và ném "AttributionError : Không thể chọn đối tượng cục bộ outs_method..inner_method ". Thêm tại đây . Nó có thể ở trong một lớp.)

(Đánh giá cao câu hỏi ban đầu được chỉ định in 'represent!'hơn là time.sleep(), nhưng không có nó, tôi nghĩ rằng một số mã đang chạy đồng thời khi không.)


Py3's ProcessPoolExecutorcũng là hai dòng ( .maptrả về một trình tạo nên bạn cần list()):

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

Với Processes đơn giản :

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

Sử dụng SimpleQueuenếu tất cả những gì bạn cần là putget. Vòng lặp đầu tiên bắt đầu tất cả các quy trình, trước khi vòng thứ hai thực hiện các queue.getcuộc gọi chặn . Tôi không nghĩ có bất kỳ lý do để gọi p.join()quá.


2

Một giải pháp đơn giản:

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

Đầu ra:

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

2

Nếu bạn đang sử dụng Python 3, bạn có thể sử dụng concurrent.futures.ProcessPoolExecutornhư một bản tóm tắt tiện lợi:

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

Đầu ra:

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

0

Tôi đã sửa đổi câu trả lời của vartec một chút vì tôi cần lấy mã lỗi từ hàm. (Cảm ơn vertec !!! đó là một mẹo tuyệt vời)

Điều này cũng có thể được thực hiện với một manager.listnhưng tôi nghĩ là tốt hơn để có nó trong một dict và lưu trữ một danh sách trong đó. Bằng cách đó, cách chúng tôi giữ chức năng và kết quả vì chúng tôi không thể chắc chắn thứ tự mà danh sách sẽ được điền.

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