Büyük pandalar veri çerçevelerinin etrafından geçmek için kuyrukları kullanarak çoklu işlem yapmanın bir yolunu oluşturmaya çalışırken yığın taşması ve web üzerinden birden çok yanıta baktım. Bana öyle geliyordu ki, her yanıt, böyle hesaplamalar yapılırken kesinlikle karşılaşılabilecek çok sayıda uç durumu dikkate almadan aynı tür çözümleri tekrarlıyordu. Sorun şu ki, aynı anda birçok şey oynanıyor. Görevlerin sayısı, çalışan sayısı, her görevin süresi ve görevin yürütülmesi sırasındaki olası istisnalar. Bunların hepsi senkronizasyonu zorlaştırır ve çoğu cevap, nasıl devam edebileceğinize değinmez. Bu, birkaç saat uğraştıktan sonra aldığım şey, umarım bu çoğu insanın yararlı bulması için yeterince genel olacaktır.
Herhangi bir kodlama örneğinden önce bazı düşünceler. Yana queue.Empty
ya da queue.qsize()
ya da başka bir benzer bir yöntem akış kontrolü, herhangi bir kod gibi güvenilmez olduğu
while True:
try:
task = pending_queue.get_nowait()
except queue.Empty:
break
sahte. Bu, milisaniyeler sonra kuyrukta başka bir görev ortaya çıksa bile çalışanı öldürecektir. Çalışan iyileşmeyecek ve bir süre sonra TÜM çalışanlar kuyruğu anlık olarak boş bulduklarından kaybolacaklar. Nihai sonuç, ana çoklu işlem işlevinin (işlemlerdeki join () işlevine sahip olan) tüm görevler tamamlanmadan geri döneceği olacaktır. Güzel. Binlerce göreviniz varsa ve birkaçı eksikse, bununla hata ayıklamada iyi şanslar.
Diğer sorun, sentinel değerlerin kullanılmasıdır. Birçok kişi kuyruğun sonunu işaretlemek için kuyruğa bir gözcü değeri eklemeyi önerdi. Ama tam olarak kime işaretlemek için? N sayıda çalışan varsa, N'nin verilen veya alınan çekirdek sayısı olduğu varsayılırsa, tek bir sentinel değer kuyruğun sonunu yalnızca bir çalışana işaretleyecektir. Diğer tüm işçiler, hiç kimse kalmadığında oturup daha fazla iş bekleyecekler. Gördüğüm tipik örnekler
while True:
task = pending_queue.get()
if task == SOME_SENTINEL_VALUE:
break
Bir işçi sentinel değerini alırken geri kalanı süresiz olarak bekleyecektir. Karşılaştığım hiçbir gönderi, TÜM işçilerin alabilmesi için, nöbetçi değeri en az işçileriniz olduğu kadar kuyruğa göndermeniz gerektiğinden bahsetmedi.
Diğer sorun, görevin yürütülmesi sırasında istisnaların ele alınmasıdır. Yine bunlar yakalanmalı ve yönetilmelidir. Ayrıca, bir completed_tasks
kuyruğunuz varsa, işin tamamlanmasına karar vermeden önce kuyrukta kaç öğe olduğunu bağımsız olarak belirleyici bir şekilde saymanız gerekir. Yine sıra boyutlarına güvenmek başarısızlıkla sonuçlanır ve beklenmedik sonuçlar verir.
Aşağıdaki örnekte, par_proc()
işlev, bu görevlerin adlandırılmış bağımsız değişkenler ve değerlerle birlikte yürütülmesi gereken işlevleri içeren bir görev listesi alacaktır.
import multiprocessing as mp
import dill as pickle
import queue
import time
import psutil
SENTINEL = None
def do_work(tasks_pending, tasks_completed):
worker_name = mp.current_process().name
while True:
try:
task = tasks_pending.get_nowait()
except queue.Empty:
print(worker_name + ' found an empty queue. Sleeping for a while before checking again...')
time.sleep(0.01)
else:
try:
if task == SENTINEL:
print(worker_name + ' no more work left to be done. Exiting...')
break
print(worker_name + ' received some work... ')
time_start = time.perf_counter()
work_func = pickle.loads(task['func'])
result = work_func(**task['task'])
tasks_completed.put({work_func.__name__: result})
time_end = time.perf_counter() - time_start
print(worker_name + ' done in {} seconds'.format(round(time_end, 5)))
except Exception as e:
print(worker_name + ' task failed. ' + str(e))
tasks_completed.put({work_func.__name__: None})
def par_proc(job_list, num_cpus=None):
if not num_cpus:
num_cpus = psutil.cpu_count(logical=False)
print('* Parallel processing')
print('* Running on {} cores'.format(num_cpus))
tasks_pending = mp.Queue()
tasks_completed = mp.Queue()
processes = []
results = []
num_tasks = 0
for job in job_list:
for task in job['tasks']:
expanded_job = {}
num_tasks = num_tasks + 1
expanded_job.update({'func': pickle.dumps(job['func'])})
expanded_job.update({'task': task})
tasks_pending.put(expanded_job)
num_workers = num_cpus
for c in range(num_workers):
tasks_pending.put(SENTINEL)
print('* Number of tasks: {}'.format(num_tasks))
for c in range(num_workers):
p = mp.Process(target=do_work, args=(tasks_pending, tasks_completed))
p.name = 'worker' + str(c)
processes.append(p)
p.start()
completed_tasks_counter = 0
while completed_tasks_counter < num_tasks:
results.append(tasks_completed.get())
completed_tasks_counter = completed_tasks_counter + 1
for p in processes:
p.join()
return results
Ve işte yukarıdaki kodu,
def test_parallel_processing():
def heavy_duty1(arg1, arg2, arg3):
return arg1 + arg2 + arg3
def heavy_duty2(arg1, arg2, arg3):
return arg1 * arg2 * arg3
task_list = [
{'func': heavy_duty1, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
{'func': heavy_duty2, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
]
results = par_proc(task_list)
job1 = sum([y for x in results if 'heavy_duty1' in x.keys() for y in list(x.values())])
job2 = sum([y for x in results if 'heavy_duty2' in x.keys() for y in list(x.values())])
assert job1 == 15
assert job2 == 21
artı bazı istisnalar dışında bir tane daha
def test_parallel_processing_exceptions():
def heavy_duty1_raises(arg1, arg2, arg3):
raise ValueError('Exception raised')
return arg1 + arg2 + arg3
def heavy_duty2(arg1, arg2, arg3):
return arg1 * arg2 * arg3
task_list = [
{'func': heavy_duty1_raises, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
{'func': heavy_duty2, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
]
results = par_proc(task_list)
job1 = sum([y for x in results if 'heavy_duty1' in x.keys() for y in list(x.values())])
job2 = sum([y for x in results if 'heavy_duty2' in x.keys() for y in list(x.values())])
assert not job1
assert job2 == 21
Umarım yardımcı olur.