显示Python多处理池imap_unordered调用的进度?


96

我有一个脚本可以通过imap_unordered()调用成功完成多处理池任务集:

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

但是,我num_tasks大约是250,000,因此join()锁将主线程锁定了10秒钟左右,我希望能够逐步回显命令行以显示主进程未锁定。就像是:

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)

是否有用于结果对象或池本身的方法来指示剩余任务数?我尝试使用multiprocessing.Value对象作为计数器(在完成任务后do_work调用counter.value += 1操作),但是在停止递增之前,计数器只能达到总值的〜85%。

Answers:


80

无需访问结果集的私有属性:

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
我仅在代码退出后才看到打印输出(不是每次迭代)。你有什么建议吗?
Hanan Shteingart 2014年

@HananShteingart:它在使用Python 2和3的系统(Ubuntu)上都可以正常工作def do_word(*a): time.sleep(.1)。如果对您不起作用,则创建一个完整的最小代码示例来说明您的问题:用文字描述您期望发生的情况以及发生的情况,提及如何运行Python脚本,OS是什么,Python版本并发布它作为一个新的问题
jfs 2014年

14
我遇到了与@HananShteingart相同的问题:这是因为我试图使用Pool.map()。我不知道,只是 imap()imap_unordered()工作在这种方式-文档只是说:“地图上的懒惰版本()”,但真正的意思是“潜在的迭代器返回的结果,因为他们进来。”
simonmacmullen

@simonmacmullen:问题和我的答案都使用imap_unordered()。Hanan的问题可能是由于sys.stderr.write('\r..')(覆盖同一行以显示进度)。
jfs 2015年

2
也可能!我主要想记录一下我所做的一个愚蠢的假设-以防其他人也读到了它。
simonmacmullen 2015年

94

我个人的最爱-在事物并行运行和提交时为您提供一个不错的进度条和完成ETA。

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
如果池返回值怎么办?
Nickpick '17

11
我在循环之前创建了一个名为result的空列表,然后在循环内部执行result.append(x)。我尝试了2个进程,并使用imap代替了map,一切都按我想要的方式进行了工作@nickpick
bs7280

2
所以我的进度栏正在迭代到新行,而不是就地进行进度,您知道为什么会这样吗?
奥斯汀

2
不要忘了pip install tqdm
T先生

3
@ bs7280由result.append(x)表示您是指result.append(_)吗?什么是x?
杰森

27

我发现在尝试检查进度时,该工作已经完成。这就是使用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()

这应该适用于所有类型的多处理,无论它们是否阻塞。


4
我认为创建了一堆线程,并且每个线程都在独立计数
nburn42 '19

1
我的函数中有函数,导致出现酸洗错误。
ojunk

21

找到了答案我自己有一些挖:以一看__dict__的的imap_unordered结果对象,我发现它有一个_index属性,它与每个任务完成的增量。因此,这适用于日志记录,并包装在while循环中:

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)

但是,我确实发现,将结果交换imap_unordered为会map_async导致执行速度更快,尽管结果对象有所不同。相反,from的结果对象map_async具有_number_left属性和ready()方法:

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
我针对Python 2.7.6进行了测试,而rs._number_left似乎是剩余的块数。因此,如果rs._chunksize不为1,则rs._number_left将不会是剩余的列表项数。
艾伦

我该把代码放在哪里?我的意思是直到知道的内容rs并且不晚才执行此操作吗?
Wakan Tanka 2015年

@WakanTanka:它在剥离额外线程后进入主脚本。在我的原始示例中,它进入了“ while”循环,该循环rs已经启动了其他线程。
午夜

1
您能否编辑您的问题和/或答案以显示最小的工作示例。我看不到rs任何循环,我是多处理新手,这会有所帮助。非常感谢你。
2015年

1
至少在中python 3.5,使用的解决方案_number_left无效。_number_left表示仍要处理的块。例如,如果我想将50个元素并行传递给我的函数,则对于具有3个进程的线程池,将_map_async()创建10个块,每个块具有5个元素。_number_left然后代表其中有多少块已完成。
mSSM

9

我知道这是一个相当老的问题,但是当我想跟踪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

基本上,您将apply_async与callbak一起使用(在这种情况下,它是将返回的值附加到列表中),因此您不必等待做其他事情。然后,在一个while循环中,检查工作进度。在这种情况下,我添加了一个小部件以使其看起来更好。

输出:

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

希望能帮助到你。


必须更改:[pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]对于(pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args)
David Przybilla

这不是真的。生成器对象在这里不起作用。已检查。
swagatam

9

如Tim所建议,您可以使用tqdmimap解决此问题。我刚刚偶然发现了这个问题并调整了imap_unordered解决方案,以便可以访问映射结果。运作方式如下:

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

如果您不关心作业返回的值,则无需将列表分配给任何变量。


4

对于寻求与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

我创建了一个自定义类来创建进度打印输出。Maby这有助于:

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

尝试这种简单的基于队列的方法,该方法也可以与池一起使用。请注意,至少在此特定进度条启动之后,打印任何东西都会导致进度条被移动。(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()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.