स्टैक ओवरफ्लो और वेब पर कई उत्तरों पर मेरी नज़र थी, जबकि बड़े पांडा पांडा के चारों ओर से गुजरने के लिए कतारों का उपयोग करके मल्टीप्रोसेसिंग करने का एक तरीका स्थापित करने की कोशिश की जा रही थी। मुझे ऐसा लग रहा था कि हर जवाब एक ही तरह के समाधानों की पुनरावृति कर रहा था, बिना किसी किनारे के मामलों की भीड़ पर विचार किए बिना, निश्चित रूप से इस तरह की गणना करते समय सामने आएगा। समस्या यह है कि एक ही समय में कई चीजें होती हैं। कार्यों की संख्या, श्रमिकों की संख्या, प्रत्येक कार्य की अवधि और कार्य निष्पादन के दौरान संभावित अपवाद। ये सभी सिंक्रोनाइज़ेशन को मुश्किल बनाते हैं और अधिकांश उत्तर यह नहीं बताते हैं कि आप इसके बारे में कैसे जा सकते हैं। इसलिए कुछ घंटों के लिए चक्कर लगाने के बाद यह मेरा काम है, उम्मीद है कि यह ज्यादातर लोगों के लिए उपयोगी होगा।
किसी भी कोडिंग उदाहरण से पहले कुछ विचार। चूंकि queue.Empty
या queue.qsize()
कोई अन्य समान विधि प्रवाह नियंत्रण के लिए अविश्वसनीय है, जैसे कोई भी कोड
while True:
try:
task = pending_queue.get_nowait()
except queue.Empty:
break
फर्जी है। यह कार्यकर्ता को मार देगा भले ही मिलीसेकंड बाद में एक और कार्य कतार में खड़ा हो जाए। कार्यकर्ता ठीक नहीं होगा और थोड़ी देर बाद सभी कर्मचारी गायब हो जाएंगे क्योंकि वे अनियमित रूप से कतार को क्षण भर खाली पाते हैं। अंतिम परिणाम यह होगा कि मुख्य मल्टीप्रोसेसिंग फ़ंक्शन (प्रक्रियाओं में शामिल होने के साथ) सभी पूर्ण किए गए कार्यों के बिना वापस आ जाएगा। अच्छा लगा। सौभाग्य सौभाग्य है कि यदि आपके पास हजारों कार्य हैं और कुछ गायब हैं।
अन्य मुद्दा प्रहरी मूल्यों का उपयोग है। कई लोगों ने कतार के अंत को ध्वजांकित करने के लिए कतार में एक प्रहरी मूल्य जोड़ने का सुझाव दिया है। लेकिन इसे किसके पास फहराना है? यदि एन श्रमिक है, तो मान लें कि एन कोर की संख्या है जो उपलब्ध है या लेना है, तो एक एकल प्रहरी मूल्य केवल एक कार्यकर्ता को कतार के अंत में ध्वजांकित करेगा। अन्य सभी कार्यकर्ता अधिक काम के इंतजार में बैठेंगे जब कोई नहीं होगा। मैंने देखा है विशिष्ट उदाहरण हैं
while True:
task = pending_queue.get()
if task == SOME_SENTINEL_VALUE:
break
एक कार्यकर्ता को प्रहरी मूल्य मिलेगा जबकि शेष अनिश्चित काल तक प्रतीक्षा करेगा। कोई पोस्ट नहीं आई जिसका मैंने उल्लेख किया है कि आपको कतार में लेटिन मूल्य को AT LEAST में जमा करने की आवश्यकता है क्योंकि आपके पास कार्यकर्ता हैं ताकि सभी को मिल जाए।
अन्य मुद्दा कार्य निष्पादन के दौरान अपवादों से निपटने का है। फिर से इन्हें पकड़ा जाना चाहिए और प्रबंधित किया जाना चाहिए। इसके अलावा, यदि आपके पास एक completed_tasks
कतार है , तो आपको स्वतंत्र रूप से एक निर्धारक तरीके से गिनना चाहिए कि आपके द्वारा तय किए गए काम को पूरा करने से पहले कितने आइटम कतार में हैं। फिर से कतार के आकार पर भरोसा करना विफल हो जाता है और अप्रत्याशित परिणाम देता है।
नीचे दिए गए उदाहरण में, par_proc()
फ़ंक्शन को उन कार्यों सहित कार्यों की एक सूची प्राप्त होगी , जिनके साथ इन कार्यों को किसी भी नामित तर्क और मूल्यों के साथ निष्पादित किया जाना चाहिए।
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
और यहाँ उपरोक्त कोड को चलाने के लिए एक परीक्षण है
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
कुछ अपवादों के साथ एक और एक
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
आशा है कि सहायक है।