Python çoklu işlem havuzu imap_unordered çağrısının ilerleyişini göster?


98

Bir imap_unordered()çağrı ile başarılı bir şekilde çoklu işlem Havuzu görevi yapan bir komut dosyam var :

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

Bununla birlikte, benim num_tasks250.000 civarında ve bu nedenle join()ana iş parçacığını 10 saniye kadar kilitliyor ve ana işlemin kilitli olmadığını göstermek için aşamalı olarak komut satırına yankılanmak istiyorum. Gibi bir şey:

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)

Sonuç nesnesi veya havuzun kendisi için kalan görevlerin sayısını gösteren bir yöntem var mı? Bir multiprocessing.Valuenesneyi sayaç olarak kullanmayı denedim ( görevini yaptıktan sonra do_workbir counter.value += 1eylem çağırır ), ancak sayaç artışı durdurmadan önce toplam değerin yalnızca ~% 85'ine ulaşıyor.

Yanıtlar:


82

Sonuç kümesinin özel niteliklerine erişmeye gerek yoktur:

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
Yazdırmayı yalnızca kod çıkışından sonra görüyorum (her yinelemede değil). Bir öneriniz var mı?
Hanan Shteingart

@HananShteingart: Sistemimde (Ubuntu) hem Python 2 hem de 3 ile iyi çalışıyor def do_word(*a): time.sleep(.1). Örnek olarak kullandım . Sizin için işe yaramazsa , sorununuzu gösteren eksiksiz bir minimal kod örneği oluşturun: ne olmasını beklediğinizi ve bunun yerine ne olacağını kelimeleri kullanarak açıklayın, Python betiğinizi nasıl çalıştırdığınızı, işletim sisteminiz, Python sürümünüz nedir ve bunu yeni bir soru olarak gönderin .
jfs

15
@HananShteingart ile aynı sorunu yaşadım: çünkü kullanmaya çalışıyordum Pool.map(). Bunun sadece imap() ve imap_unordered()bu şekilde çalıştığının farkında değildim - dokümantasyon sadece "map () 'in daha tembel bir versiyonu" diyor ama gerçekten "temel yineleyici geldikçe sonuçları döndürüyor" anlamına geliyor.
simonmacmullen

@simonmacmullen: hem soru hem de cevabım kullanılır imap_unordered(). Hanan'ın sorunu muhtemelen sys.stderr.write('\r..')(ilerlemeyi göstermek için aynı satırın üzerine yazmak) kaynaklanmaktadır.
jfs

2
Bu da mümkün! Esas olarak yaptığım aptalca bir varsayımı belgelemek istedim - bunu okuyan başka biri de yapmış olabilirse diye.
simonmacmullen

96

Benim kişisel favorim - işler paralel ilerlerken size güzel bir ilerleme çubuğu ve tamamlama tahmini varış süresi verir.

from multiprocessing import Pool
import tqdm

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

65
havuz bir değer döndürürse ne olur?
Nickpick

11
Döngüden önce result adında boş bir liste oluşturdum ve döngünün içinde sadece result.append (x) yapıyorum. Bunu 2 işlemle denedim ve harita yerine imap kullandım ve her şey istediğim gibi çalıştı @nickpick
bs7280

2
bu yüzden ilerleme çubuğum yerinde ilerlemek yerine yeni satırlara yineleniyor, bunun neden olabileceği hakkında bir fikriniz var mı?
Austin

2
unutmayınpip install tqdm
Bay T

3
@ bs7280 result.append (x) ile sonuç.append (_) mi demek istediniz? X nedir?
jason

27

İlerlemesini kontrol etmeye çalıştığımda işin zaten yapılmış olduğunu buldum. Bu, tqdm kullanarak benim için çalıştı .

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

Bu, engelleyip engellemesinler, çoklu işlemenin tüm çeşitleriyle çalışmalıdır.


5
Sanırım bir sürü iş parçacığı yaratıyor ve her iş parçacığı bağımsız olarak sayılıyor
nburn42

1
Asitleme hatasına neden olan işlevler içinde işlevlerim var.
ojunk

22

Biraz daha kazma ile bir cevap buldum: bir göz alarak __dict__bir imap_unorderedsonucu nesnesi, bunun sahiptir bulundu _indexniteliği olduğu her görev tamamlanması ile artar. Bu, günlük kaydı için çalışır, whiledöngü içinde sarılır :

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)

Bununla birlikte, sonuç nesnesi biraz farklı olsa da, imap_unorderedbir için değiştirmenin map_asyncçok daha hızlı yürütme ile sonuçlandığını buldum . Bunun yerine, sonuç nesnesinin map_asyncbir _number_leftözniteliği ve bir ready()yöntemi vardır:

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
Bunu Python 2.7.6 için test ettim ve rs._number_left kalan parça sayısı gibi görünüyor. Dolayısıyla, rs._chunksize 1 değilse, rs._number_left kalan liste öğesi sayısı olmayacaktır.
Allen

Bu kodu nereye koymalıyım? Demek istediğim, içeriği rsbilinene kadar bu yapılmaz ve biraz geç kalır mı?
Wakan Tanka

@WakanTanka: Fazladan konuları kapattıktan sonra ana senaryoya girer. Benim orijinal rsörneğimde, diğer iş parçacıkları zaten başlatılmış olan "while" döngüsüne giriyor .
MidnightLightning

1
Asgari çalışma örneğini göstermek için lütfen sorunuzu ve / veya yanıtınızı düzenler misiniz? rsHerhangi bir döngüde görmüyorum , acemi çoklu işlem yapıyorum ve bu yardımcı olacaktır. Çok teşekkür ederim.
Wakan Tanka

1
En azından içinde python 3.5, kullanarak çözüm _number_leftişe yaramıyor. _number_leftişlenecek kalan parçaları temsil eder. Örneğin, fonksiyonuma paralel olarak 50 eleman geçirilmesini istersem, 3 işlemli bir iş parçacığı havuzu için _map_async()her biri 5 elemanlı 10 parça oluşturur. _number_leftdaha sonra bu parçalardan kaçının tamamlandığını gösterir.
mSSM

9

Bunun oldukça eski bir soru olduğunu biliyorum, ancak işte python'daki bir görev havuzunun ilerlemesini izlemek istediğimde yaptığım şey.

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

Temel olarak, bir callbak ile apply_async kullanırsınız (bu durumda, döndürülen değeri bir listeye eklemek içindir), böylece başka bir şey yapmak için beklemeniz gerekmez. Ardından, bir süre döngüsü içinde işin ilerleyişini kontrol edersiniz. Bu durumda, daha güzel görünmesi için bir widget ekledim.

Çıktı:

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

Umarım yardımcı olur.


değişiklik gerekiyor: [pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]for(pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args)
David Przybilla

Bu doğru değil. Bir jeneratör nesnesi burada çalışmayacaktır. Kontrol.
swagatam

9

Tim tarafından önerildiği gibi, bu sorunu çözmek için tqdmve kullanabilirsiniz imap. imap_unorderedHaritalamanın sonuçlarına erişebilmek için bu soruna henüz rastladım ve çözümü değiştirdim . Şu şekilde çalışır:

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

İşlerinizden dönen değerleri önemsemiyorsanız, listeyi herhangi bir değişkene atamanıza gerek yoktur.


4

ile çalışan basit bir çözüm arayanlar için 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

Bir ilerleme çıktısı oluşturmak için özel bir sınıf oluşturdum. Maby bu yardımcı olur:

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

Havuzlama ile de kullanılabilen bu basit Kuyruk tabanlı yaklaşımı deneyin. İlerleme çubuğunun başlatılmasından sonra herhangi bir şeyi yazdırmanın, en azından bu belirli ilerleme çubuğu için taşınmasına neden olacağını unutmayın. (PyPI'nin ilerlemesi 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()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.