แสดงความคืบหน้าของพูลการประมวลผลหลายตัวของ Python imap_unordered call?


104

ฉันมีสคริปต์ที่ประสบความสำเร็จในการทำชุดงานพูลหลายกระบวนการด้วยการ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% ของมูลค่าทั้งหมดเท่านั้นก่อนที่จะหยุดการเพิ่มขึ้น

คำตอบ:


84

ไม่จำเป็นต้องเข้าถึงแอตทริบิวต์ส่วนตัวของชุดผลลัพธ์:

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

@HananShteingart: มันทำงานได้ดีบนระบบของฉัน (Ubuntu) กับทั้ง Python 2 และ 3 ฉันใช้def do_word(*a): time.sleep(.1)เป็นตัวอย่าง หากไม่ได้ผลสำหรับคุณให้สร้างตัวอย่างโค้ดขั้นต่ำที่สมบูรณ์ซึ่งแสดงให้เห็นถึงปัญหาของคุณ: อธิบายโดยใช้คำว่าคุณคาดว่าจะเกิดอะไรขึ้นและสิ่งที่เกิดขึ้นแทนพูดถึงวิธีที่คุณเรียกใช้สคริปต์ Python ของคุณระบบปฏิบัติการเวอร์ชัน Python ของคุณคืออะไร และโพสต์เป็นคำถามใหม่
jfs

15
ผมมีปัญหาเช่นเดียวกับ @HananShteingart: Pool.map()มันเป็นเพราะผมพยายามที่จะใช้ ฉันไม่ทราบว่ามีเพียงอย่างเดียว imap()และimap_unordered()ทำงานในลักษณะนี้ - เอกสารระบุเพียงว่า "แผนที่เวอร์ชันที่ขี้เกียจกว่า ()" แต่จริงๆแล้วหมายความว่า "ตัววนซ้ำที่สำคัญจะส่งคืนผลลัพธ์เมื่อเข้ามา"
simonmacmullen

@simonmacmullen imap_unordered()ทั้งคำถามและการใช้คำตอบของฉัน ปัญหาของ Hanan น่าจะเกิดจากsys.stderr.write('\r..')(เขียนทับบรรทัดเดียวกันเพื่อแสดงความคืบหน้า)
jfs

2
ยังเป็นไปได้! ฉันต้องการบันทึกข้อสันนิษฐานโง่ ๆ ที่ฉันทำไว้เป็นหลักในกรณีที่มีคนอื่นอ่านสิ่งนี้ก็ทำเช่นกัน
simonmacmullen

102

รายการโปรดส่วนตัวของฉัน - ช่วยให้คุณมีแถบความคืบหน้าเล็ก ๆ น้อย ๆ และ 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

66
จะเกิดอะไรขึ้นถ้าพูลส่งคืนค่า
Nickpick

11
ฉันสร้างรายการว่างที่เรียกว่าผลลัพธ์ก่อนลูปจากนั้นภายในลูปก็ทำผลลัพธ์ append (x) ฉันลองใช้ 2 กระบวนการและใช้ imap แทนแผนที่และทุกอย่างทำงานได้ตามที่ฉันต้องการ @nickpick
bs7280

2
ดังนั้นแถบความคืบหน้าของฉันจึงวนซ้ำไปยังบรรทัดใหม่แทนที่จะดำเนินการในตำแหน่งมีความคิดอย่างไร
Austin

2
อย่าลืมpip install tqdm
Mr. 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()

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


5
ฉันคิดว่าสร้างเธรดจำนวนมากและแต่ละเธรดจะนับแยกกัน
nburn42

1
ฉันมีฟังก์ชันภายในฟังก์ชันซึ่งทำให้เกิดข้อผิดพลาดในการดอง
ถึง

22

พบคำตอบที่ตัวเองด้วยบางขุดขึ้น: Taking ดูที่__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_unorderedfor a map_asyncทำให้การดำเนินการเร็วขึ้นมากแม้ว่าวัตถุผลลัพธ์จะแตกต่างกันเล็กน้อย แต่วัตถุผลลัพธ์จาก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 จะไม่ใช่จำนวนรายการที่เหลืออยู่
Allen

ฉันควรใส่รหัสนี้ไว้ที่ไหน? ฉันหมายความว่าสิ่งนี้จะไม่ถูกดำเนินการจนกว่าจะทราบเนื้อหาrsและช้าไปหน่อยหรือไม่?
Wakan Tanka

@WakanTanka: มันไปในสคริปต์หลักหลังจากที่มันหมุนออกจากเธรดพิเศษ ในตัวอย่างเดิมของฉันจะอยู่ในลูป "while" ซึ่งrsได้เปิดใช้งานเธรดอื่นแล้ว
MidnightLightning

1
คุณช่วยแก้ไขคำถามและ / หรือคำตอบของคุณเพื่อแสดงตัวอย่างการทำงานขั้นต่ำได้ไหม ฉันไม่เห็นrsในวงใด ๆ ฉันกำลังประมวลผลมือใหม่และสิ่งนี้จะช่วยได้ ขอบคุณมาก.
Wakan Tanka

1
อย่างน้อยในpython 3.5การแก้ปัญหาโดยใช้_number_leftไม่ได้ผล _number_leftแสดงถึงชิ้นส่วนที่ยังคงต้องดำเนินการ ตัวอย่างเช่นถ้าฉันต้องการให้ 50 องค์ประกอบส่งผ่านไปยังฟังก์ชันของฉันแบบขนานสำหรับเธรดพูลที่มี 3 กระบวนการ_map_async()จะสร้าง 10 ชิ้นโดยแต่ละองค์ประกอบ 5 รายการ _number_leftจากนั้นแสดงจำนวนชิ้นส่วนเหล่านี้ที่เสร็จสมบูรณ์
mSSM

10

ฉันรู้ว่านี่เป็นคำถามที่ค่อนข้างเก่า แต่นี่คือสิ่งที่ฉันกำลังทำเมื่อต้องการติดตามความคืบหน้าของกลุ่มงานใน 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 (ในกรณีนี้คือการต่อท้ายค่าที่ส่งคืนไปยังรายการ) ดังนั้นคุณจึงไม่ต้องรอทำอย่างอื่น จากนั้นคุณตรวจสอบความคืบหน้าของงานภายในระยะเวลาหนึ่ง ในกรณีนี้ฉันเพิ่มวิดเจ็ตเพื่อให้ดูดีขึ้น

ผลลัพธ์:

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 แนะนำคุณสามารถใช้tqdmและimapเพื่อแก้ปัญหานี้ได้ ฉันเพิ่งสะดุดกับปัญหานี้และปรับ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)))

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


5

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