Hiển thị tiến trình của lệnh gọi imap_unordered pool đa xử lý trong Python?


95

Tôi có một tập lệnh đang thực hiện thành công một nhóm tác vụ Nhóm đa xử lý với imap_unordered()lệnh gọi:

p = multiprocessing.Pool()
rs = p.imap_unordered(do_work, xrange(num_tasks))
p.close() # No more work
p.join() # Wait for completion

Tuy nhiên, của tôi num_taskslà khoảng 250.000 và do đó, chuỗi join()khóa chính trong 10 giây hoặc lâu hơn, và tôi muốn có thể lặp lại dòng lệnh một cách tăng dần để hiển thị quá trình chính không bị khóa. Cái gì đó như:

p = multiprocessing.Pool()
rs = p.imap_unordered(do_work, xrange(num_tasks))
p.close() # No more work
while (True):
  remaining = rs.tasks_remaining() # How many of the map call haven't been done yet?
  if (remaining == 0): break # Jump out of while loop
  print "Waiting for", remaining, "tasks to complete..."
  time.sleep(2)

Có phương thức nào cho đối tượng kết quả hoặc chính nhóm cho biết số lượng tác vụ còn lại không? Tôi đã thử sử dụng một multiprocessing.Valueđối tượng làm bộ đếm ( do_workgọi một counter.value += 1hành động sau khi thực hiện tác vụ của nó), nhưng bộ đếm chỉ nhận được ~ 85% tổng giá trị trước khi ngừng tăng.

Câu trả lời:


80

Không cần truy cập các thuộc tính riêng tư của tập kết quả:

from __future__ import division
import sys

for i, _ in enumerate(p.imap_unordered(do_work, xrange(num_tasks)), 1):
    sys.stderr.write('\rdone {0:%}'.format(i/num_tasks))

7
Tôi chỉ thấy bản in sau khi thoát mã (không phải mọi lần lặp lại). Bạn có một gợi ý?
Hanan Shteingart

@HananShteingart: Nó hoạt động tốt trên hệ thống của tôi (Ubuntu) với cả Python 2 và 3. Tôi đã sử dụng def do_word(*a): time.sleep(.1)làm ví dụ. Nếu nó không hiệu quả với bạn thì hãy tạo một ví dụ mã tối thiểu hoàn chỉnh để giải thích vấn đề của bạn: mô tả bằng từ ngữ những gì bạn mong đợi sẽ xảy ra và những gì sẽ xảy ra thay thế, đề cập đến cách bạn chạy tập lệnh Python của mình, hệ điều hành của bạn là gì, phiên bản Python và đăng nó dưới dạng một câu hỏi mới .
jfs

13
Tôi đã gặp vấn đề tương tự như @HananShteingart: đó là do tôi đang cố gắng sử dụng Pool.map(). Tôi đã không nhận ra điều đó duy nhất imap()imap_unordered()làm việc theo cách này - tài liệu chỉ nói "Phiên bản lười hơn của map ()" nhưng thực sự có nghĩa là "trình lặp bên dưới trả về kết quả khi chúng xuất hiện".
simonmacmullen

@simonmacmullen: cả câu hỏi và câu trả lời của tôi đều sử dụng imap_unordered(). Vấn đề của Hanan có lẽ là do sys.stderr.write('\r..')(ghi đè lên cùng một dòng để hiển thị tiến trình).
jfs

2
Cũng có thể! Tôi chủ yếu muốn ghi lại một giả định ngu ngốc mà tôi đã đưa ra - trong trường hợp bất kỳ ai khác đang đọc điều này cũng mắc phải.
simonmacmullen

94

Yêu thích cá nhân của tôi - cung cấp cho bạn một thanh tiến trình nhỏ và ETA hoàn thành trong khi mọi thứ chạy và cam kết song song.

from multiprocessing import Pool
import tqdm

pool = Pool(processes=8)
for _ in tqdm.tqdm(pool.imap_unordered(do_work, tasks), total=len(tasks)):
    pass

64
điều gì sẽ xảy ra nếu pool trả về một giá trị?
Nickpick

11
Tôi đã tạo một danh sách trống được gọi là result trước vòng lặp, sau đó bên trong vòng lặp chỉ cần thực hiện result.append (x). Tôi cố gắng này với 2 quy trình và imap được sử dụng thay cho bản đồ và tất cả mọi thứ đã làm việc như tôi muốn nó @nickpick
bs7280

2
vì vậy thanh tiến trình của tôi đang lặp lại thành các dòng mới thay vì tiến trình tại chỗ, bất kỳ ý tưởng nào tại sao điều này có thể xảy ra?
Austin

2
đừng quênpip install tqdm
Mr. T

3
@ bs7280 Theo result.append (x) có phải ý bạn là result.append (_) không? X là gì?
jason

27

Tôi thấy rằng công việc đã hoàn thành vào thời điểm tôi cố gắng kiểm tra tiến độ của nó. Đây là những gì làm việc cho tôi bằng cách sử dụng tqdm .

pip install tqdm

from multiprocessing import Pool
from tqdm import tqdm

tasks = range(5)
pool = Pool()
pbar = tqdm(total=len(tasks))

def do_work(x):
    # do something with x
    pbar.update(1)

pool.imap_unordered(do_work, tasks)
pool.close()
pool.join()
pbar.close()

Điều này sẽ hoạt động với tất cả các hương vị của quá trình đa xử lý, cho dù chúng có chặn hay không.


4
Tôi nghĩ rằng tạo ra một loạt các chủ đề, và mỗi thread được đếm một cách độc lập
nburn42

1
Tôi có các chức năng trong các chức năng dẫn đến lỗi kén.
ojunk

21

Tôi đã tự tìm ra câu trả lời với một số lần đào sâu hơn: Xem __dict__qua imap_unorderedđối tượng kết quả, tôi thấy nó có một _indexthuộc tính tăng dần sau mỗi lần hoàn thành nhiệm vụ. Vì vậy, điều này hoạt động để ghi nhật ký, được bao bọc trong whilevòng lặp:

p = multiprocessing.Pool()
rs = p.imap_unordered(do_work, xrange(num_tasks))
p.close() # No more work
while (True):
  completed = rs._index
  if (completed == num_tasks): break
  print "Waiting for", num_tasks-completed, "tasks to complete..."
  time.sleep(2)

Tuy nhiên, tôi đã nhận thấy rằng việc hoán đổi imap_unorderedlấy một map_asyncdẫn đến việc thực thi nhanh hơn nhiều, mặc dù đối tượng kết quả hơi khác một chút. Thay vào đó, đối tượng kết quả từ map_asynccó một _number_leftthuộc tính và một ready()phương thức:

p = multiprocessing.Pool()
rs = p.map_async(do_work, xrange(num_tasks))
p.close() # No more work
while (True):
  if (rs.ready()): break
  remaining = rs._number_left
  print "Waiting for", remaining, "tasks to complete..."
  time.sleep(0.5)

3
Tôi đã thử nghiệm điều này cho Python 2.7.6 và rs._number_left dường như là số khối còn lại. Vì vậy, nếu rs._chunksize không phải là 1 thì rs._number_left sẽ không phải là số mục danh sách còn lại.
Allen

Tôi nên đặt mã này ở đâu? Ý tôi là điều này không được thực thi cho đến khi nội dung của rsis knowns và có hơi muộn hay không?
Wakan Tanka

@WakanTanka: Nó đi vào tập lệnh chính sau khi nó tách các chủ đề phụ. Trong ví dụ ban đầu của tôi, nó đi trong vòng lặp "while", nơi rsđã khởi chạy các luồng khác.
MidnightLightning

1
Bạn có thể vui lòng chỉnh sửa câu hỏi và / hoặc câu trả lời của mình để hiển thị ví dụ làm việc tối thiểu. Tôi không thấy rstrong bất kỳ vòng lặp nào, tôi đang xử lý nhiều người mới và điều này sẽ hữu ích. Cảm ơn rât nhiều.
Wakan Tanka

1
Ít nhất python 3.5, giải pháp sử dụng _number_leftkhông hoạt động. _number_leftđại diện cho các phần vẫn được xử lý. Ví dụ: nếu tôi muốn có 50 phần tử được truyền song song cho hàm của mình, thì đối với một nhóm luồng có 3 quy trình _map_async()sẽ tạo ra 10 phần với mỗi phần 5 phần tử. _number_leftsau đó đại diện cho bao nhiêu phần trong số này đã được hoàn thành.
mSSM

9

Tôi biết rằng đây là một câu hỏi khá cũ, nhưng đây là những gì tôi đang làm khi muốn theo dõi tiến trình của một nhóm các nhiệm vụ trong python.

from progressbar import ProgressBar, SimpleProgress
import multiprocessing as mp
from time import sleep

def my_function(letter):
    sleep(2)
    return letter+letter

dummy_args = ["A", "B", "C", "D"]
pool = mp.Pool(processes=2)

results = []

pbar = ProgressBar(widgets=[SimpleProgress()], maxval=len(dummy_args)).start()

r = [pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]

while len(results) != len(dummy_args):
    pbar.update(len(results))
    sleep(0.5)
pbar.finish()

print results

Về cơ bản, bạn sử dụng apply_async với callbak (trong trường hợp này, nó là để nối giá trị trả về vào danh sách), vì vậy bạn không cần phải chờ đợi để làm điều gì khác. Sau đó, trong vòng lặp lại, bạn kiểm tra tiến trình của công việc. Trong trường hợp này, tôi đã thêm một widget để làm cho nó trông đẹp hơn.

Đầu ra:

4 of 4                                                                         
['AA', 'BB', 'CC', 'DD']

Hy vọng nó giúp.


gotta thay đổi: [pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]cho(pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args)
David Przybilla

Đo không phải sự thật. Một đối tượng máy phát điện sẽ không hoạt động ở đây. Đã kiểm tra.
swagatam

9

Theo gợi ý của Tim, bạn có thể sử dụng tqdmimapđể giải quyết vấn đề này. Tôi vừa gặp phải vấn đề này và đã điều chỉnh imap_unorderedgiải pháp để tôi có thể truy cập kết quả của ánh xạ. Đây là cách nó hoạt động:

from multiprocessing import Pool
import tqdm

pool = multiprocessing.Pool(processes=4)
mapped_values = list(tqdm.tqdm(pool.imap_unordered(do_work, range(num_tasks)), total=len(values)))

Trong trường hợp bạn không quan tâm đến các giá trị trả về từ các công việc của mình, bạn không cần gán danh sách cho bất kỳ biến nào.


4

cho bất kỳ ai đang tìm kiếm một giải pháp đơn giản làm việc với Pool.apply_async():

from multiprocessing import Pool
from tqdm import tqdm
from time import sleep


def work(x):
    sleep(0.5)
    return x**2

n = 10

p = Pool(4)
pbar = tqdm(total=n)
res = [p.apply_async(work, args=(
    i,), callback=lambda _: pbar.update(1)) for i in range(n)]
results = [p.get() for p in res]

3

Tôi đã tạo một lớp tùy chỉnh để tạo bản in tiến trình. Maby điều này giúp:

from multiprocessing import Pool, cpu_count


class ParallelSim(object):
    def __init__(self, processes=cpu_count()):
        self.pool = Pool(processes=processes)
        self.total_processes = 0
        self.completed_processes = 0
        self.results = []

    def add(self, func, args):
        self.pool.apply_async(func=func, args=args, callback=self.complete)
        self.total_processes += 1

    def complete(self, result):
        self.results.extend(result)
        self.completed_processes += 1
        print('Progress: {:.2f}%'.format((self.completed_processes/self.total_processes)*100))

    def run(self):
        self.pool.close()
        self.pool.join()

    def get_results(self):
        return self.results

1

Hãy thử cách tiếp cận dựa trên Hàng đợi đơn giản này, cũng có thể được sử dụng với tính năng gộp. Lưu ý rằng việc in bất kỳ thứ gì sau khi bắt đầu thanh tiến trình sẽ khiến nó bị di chuyển, ít nhất là đối với thanh tiến trình cụ thể này. (Tiến trình của PyPI 1.5)

import time
from progress.bar import Bar

def status_bar( queue_stat, n_groups, n ):

    bar = Bar('progress', max = n)  

    finished = 0
    while finished < n_groups:

        while queue_stat.empty():
            time.sleep(0.01)

        gotten = queue_stat.get()
        if gotten == 'finished':
            finished += 1
        else:
            bar.next()
    bar.finish()


def process_data( queue_data, queue_stat, group):

    for i in group:

        ... do stuff resulting in new_data

        queue_stat.put(1)

    queue_stat.put('finished')  
    queue_data.put(new_data)

def multiprocess():

    new_data = []

    groups = [[1,2,3],[4,5,6],[7,8,9]]
    combined = sum(groups,[])

    queue_data = multiprocessing.Queue()
    queue_stat = multiprocessing.Queue()

    for i, group in enumerate(groups): 

        if i == 0:

            p = multiprocessing.Process(target = status_bar,
                args=(queue_stat,len(groups),len(combined)))
                processes.append(p)
                p.start()

        p = multiprocessing.Process(target = process_data,
        args=(queue_data, queue_stat, group))
        processes.append(p)
        p.start()

    for i in range(len(groups)):
        data = queue_data.get() 
        new_data += data

    for p in processes:
        p.join()
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.