Python Multiprocessing: थ्रेड्स और प्रॉसेसेज़ के लिए एक मार्गदर्शिका
Python के multiprocessing मॉड्यूल के साथ थ्रेड्स और प्रॉसेसेज़ को प्रबंधित करना सीखें। समानांतर प्रोग्रामिंग की प्रमुख तकनीकें जानें। उदाहरणों के साथ अपने कोड की दक्षता बढ़ाएँ।
Python की स्टैंडर्ड लाइब्रेरी में कई बिल्ट-इन पैकेज शामिल होते हैं, जिनसे डेवलपर्स तुरंत भाषा के फ़ायदों का लाभ उठाना शुरू कर सकते हैं।
ऐसा ही एक पैकेज multiprocessing मॉड्यूल है, जो सिस्टम को एक साथ कई प्रॉसेस चलाने में सक्षम बनाता है। दूसरे शब्दों में, डेवलपर्स एप्लिकेशन को छोटे-छोटे थ्रेड्स में बाँट सकते हैं, जो उनकी Python कोड से स्वतंत्र रूप से चल सकते हैं। ये थ्रेड्स या प्रॉसेसेज़ फिर ऑपरेटिंग सिस्टम द्वारा प्रोसेसर को आवंटित किए जाते हैं, जिससे वे समानांतर में चल पाते हैं और आपके Python प्रोग्राम्स के परफ़ॉर्मेंस और दक्षता में सुधार होता है।
यदि थ्रेड्स, प्रॉसेसेज़, प्रोसेसर्स आदि शब्द आपको अपरिचित लग रहे हैं, तो चिंता न करें। इस लेख में हम प्रॉसेस की परिभाषा, थ्रेड्स से उनके अंतर, और multiprocessing मॉड्यूल का उपयोग कैसे करें—इन सब पर चर्चा करेंगे।
Python में प्रॉसेसेज़ क्या होते हैं?
प्रॉसेसेज़ और थ्रेड्स की अवधारणा को समझना बेहद उपयोगी है, ताकि आप बेहतर तरीके से समझ सकें कि ऑपरेटिंग सिस्टम अलग-अलग निष्पादन चरणों में प्रोग्राम्स को कैसे प्रबंधित करता है। एक प्रॉसेस मूलतः किसी कंप्यूटर प्रोग्राम का संदर्भ होता है। प्रत्येक प्रोग्राम का एक प्रॉसेस उससे संबद्ध होता है।
जैसे ही आप यह लेख पढ़ रहे हैं, बहुत संभव है कि आपके कंप्यूटर पर एक ही समय में कई प्रॉसेसेज़ चल रहे हों, भले ही आपने कुछ ही प्रोग्राम खोले हों—ऐसा इसलिए है क्योंकि अधिकतर ऑपरेटिंग सिस्टम बैकग्राउंड में कई टास्क चलाते रहते हैं। उदाहरण के लिए, इस समय आपके पास केवल तीन प्रोग्राम खुले हो सकते हैं, लेकिन आपका कंप्यूटर एक साथ 30 से अधिक सक्रिय प्रॉसेसेज़ चला रहा हो सकता है।
आपके कंप्यूटर पर वर्तमान में चल रहे सक्रिय प्रॉसेसेज़ कैसे देखें, यह आपके ऑपरेटिंग सिस्टम पर निर्भर करता है:
Windows पर: Ctrl+Shift+Esc टास्क मैनेजर शुरू करेगा
Mac पर: Spotlight Search खोलें और "Activity Monitor" टाइप करें, फिर Return दबाएँ
Linux पर: एप्लिकेशन मेन्यू पर क्लिक करें और System Monitor खोजें।
Python वास्तविक सिस्टम-स्तर के प्रॉसेसेज़ तक पहुँच प्रदान करता है। multiprocessing मॉड्यूल की Process क्लास का एक इंस्टेंस बनाने से डेवलपर्स को Python के माध्यम से अंतर्निहित नेटिव प्रॉसेस को संदर्भित करने में सक्षम बनता है। जब कोई प्रॉसेस शुरू होता है, तो परदे के पीछे एक नया नेटिव प्रॉसेस बनता है। एक Python प्रॉसेस का जीवनचक्र तीन चरणों से बना होता है: नए प्रॉसेस की शुरुआत, चल रहा प्रॉसेस, और समाप्त हुआ प्रॉसेस—हम प्रत्येक चरण को कवर करेंगे।
सभी प्रॉसेसेज़ एक या अधिक थ्रेड्स से मिलकर बने होते हैं। याद है, हमने कहा था कि “एक प्रॉसेस मूलतः कंप्यूटर प्रोग्राम का संदर्भ है”? दरअसल, प्रत्येक Python प्रोग्राम एक प्रॉसेस होता है, जिसमें एक डिफ़ॉल्ट थ्रेड होता है जिसे मुख्य थ्र्रेड (main thread) कहा जाता है। मुख्य थ्रेड आपके Python प्रोग्राम्स के भीतर दिए गए निर्देशों को निष्पादित करने का ज़िम्मेदार होता है। हालाँकि, यह याद रखना महत्वपूर्ण है कि प्रॉसेसेज़ और थ्रेड्स अलग-अलग होते हैं।
Multiprocessing बनाम Threading
और सटीक रूप से कहें तो, Python के इंटरप्रेटर का एक इंस्टेंस—वह टूल जो Python में लिखे गए कोड को कंप्यूटर द्वारा समझी जाने वाली भाषा में बदलता है—एक प्रॉसेस के बराबर होता है। एक प्रॉसेस में कम से कम एक थ्रेड होता है, जिसे Python में “main thread” कहा जाता है, हालाँकि उसी प्रॉसेस के भीतर अन्य थ्रेड्स भी बनाए जा सकते हैं—प्रॉसेस के भीतर बनाए गए सभी अन्य थ्रेड्स उसी प्रॉसेस से संबद्ध होंगे।
थ्रेड आपके Python प्रोग्राम के निष्पादन का प्रतिनिधित्व करता है, और जैसे ही सभी non-background थ्रेड्स समाप्त हो जाते हैं, Python प्रॉसेस भी समाप्त हो जाता है।
Process: एक प्रॉसेस Python इंटरप्रेटर का एक इंस्टेंस है, जिसमें कम से कम एक थ्रेड होता है जिसे main thread कहा जाता है।
Thread: यह दर्शाता है कि एक Python प्रोग्राम किसी Python प्रॉसेस के भीतर कैसे निष्पादित होता है।
Python में दो बेहद समान क्लासेज़ हैं जो हमें प्रॉसेसेज़ और थ्रेड्स पर अधिक नियंत्रण देती हैं: multiprocessing.Process और threading.Thread।
आइए इनकी समानताओं और भिन्नताओं की समीक्षा करें।
समानताएँ
Concurrency
Concurrency वह अवधारणा है जिसमें प्रोग्राम के अलग-अलग हिस्से क्रम से बाहर या आंशिक क्रम में निष्पादित हो सकते हैं, बिना अंतिम परिणाम पर असर डाले। दोनों क्लासेज़ मूल रूप से कन्करेंसी के लिए ही बनाई गई थीं।
Concurrency primitives के लिए समर्थन
multiprocessing.Process और threading.Thread क्लासेज़ समान concurrency primitives का समर्थन करती हैं—ऐसे टूल जो थ्रेड्स और प्रॉसेसेज़ का समन्वय और सिंक्रोनाइज़ेशन सक्षम करते हैं।
एकरूप API
एक बार जब आप multiprocessing.Process API समझ लेते हैं, तो आप वही ज्ञान threading.Thread API पर भी लागू कर सकते हैं, और इसके विपरीत। इन्हें जानबूझकर इसी तरह डिज़ाइन किया गया है।
अंतर
कार्य-क्षमता
हालाँकि इनकी APIs समान हैं, लेकिन प्रॉसेसेज़ और थ्रेड्स अलग-अलग होते हैं। प्रॉसेस, थ्रेड की तुलना में उच्च-स्तरीय अमूर्तन है: प्रॉसेस कंप्यूटर प्रोग्राम का संदर्भ होता है, और थ्रेड किसी प्रॉसेस का हिस्सा होता है। यह अंतर इन क्लासेज़ में निहित है। इसलिए, ये क्लासेज़ अंतर्निहित ऑपरेटिंग सिस्टम द्वारा प्रबंधित दो अलग-अलग नेटिव फ़ंक्शंस का प्रतिनिधित्व करती हैं।
साझा स्टेट तक पहुँच
दोनों क्लासेज़ साझा स्टेट तक अलग-अलग तरीके से पहुँचती हैं। चूँकि थ्रेड्स किसी प्रॉसेस से संबद्ध होते हैं, वे उसी प्रॉसेस के भीतर मेमोरी साझा कर सकते हैं। इसलिए, किसी नए थ्रेड में निष्पादित फ़ंक्शन को उसी प्रॉसेस के भीतर मौजूद डेटा और स्टेट तक पहुँच बनी रहती है। थ्रेड्स के बीच स्टेट साझा करने के इस तरीके को “shared memory” कहा जाता है और यह काफ़ी सीधा है। इसके विपरीत, प्रॉसेसेज़ के बीच स्टेट साझा करना अधिक जटिल है: स्टेट को सीरियलाइज़ करके प्रॉसेसेज़ के बीच भेजना पड़ता है। दूसरे शब्दों में, प्रॉसेसेज़ के पास अलग-अलग मेमोरी होती है, इसलिए वे shared memory का उपयोग नहीं करते। इसके बजाय, प्रॉसेसेज़ के बीच साझा करना “inter-process communication” नामक तकनीक से होता है, और Python में इसे करने के लिए multiprocessing.Pipe या multiprocessing.Queue जैसे स्पष्ट टूल्स की आवश्यकता होती है।
GIL
Python Global Interpreter Lock (GIL) एक लॉक है जो केवल एक थ्रेड को ही एक समय में Python इंटरप्रेटर पर नियंत्रण रखने देता है। कई थ्रेड्स GIL के अधीन होते हैं, जिसके कारण Python में multithreading करना अक्सर अच्छा विचार नहीं होता: CPython इंटरप्रेटर पर multithreading के माध्यम से सच्चा मल्टी-कोर निष्पादन समर्थित नहीं है। हालाँकि, प्रॉसेसेज़ GIL के अधीन नहीं होते, क्योंकि GIL प्रत्येक Python प्रॉसेस के भीतर तो होता है, लेकिन प्रॉसेसेज़ के पार नहीं।
उपयोग मामलों की दृष्टि से, जहाँ प्रोग्राम CPU-इंटेंसिव हो और उसे किसी IO या यूज़र इंटरैक्शन की आवश्यकता न हो, वहाँ multiprocessing प्रायः threading से बेहतर रहता है। वहीं, IO या नेटवर्क-बाउंड प्रोग्राम्स तथा ऐसे परिदृश्यों में जहाँ लक्ष्य एप्लिकेशन की प्रतिक्रियाशीलता बढ़ाना हो, threading बेहतर समाधान है।
Python Multiprocessing के लाभ
प्रोसेसर को एक उद्यमी की तरह समझें। जैसे-जैसे उद्यमी का व्यवसाय बढ़ता है, व्यवसाय की वृद्धि के साथ तालमेल बनाए रखने के लिए अधिक कार्यों का प्रबंधन करना पड़ता है। यदि उद्यमी यह तय करता है कि वह सारे कार्य अकेले ही करेगा (जैसे अकाउंटिंग, सेल्स, मार्केटिंग, इनोवेशन आदि), तो वह व्यवसाय की कुल दक्षता और प्रदर्शन को बाधित करने का जोखिम उठाता है, क्योंकि एक व्यक्ति एक समय में सीमित ही काम कर सकता है। उदाहरण के लिए, इनोवेशन कार्यों पर जाने से पहले उसे सेल्स कार्यों को रोकना पड़ेगा—इसे टास्क्स को “अनुक्रम में” चलाना कहा जाता है।
अधिकांश उद्यमी समझते हैं कि सब कुछ अकेले करना बुरा विचार है। नतीजतन, वे आमतौर पर बढ़ते कार्यभार को संतुलित करने के लिए अलग-अलग विभागों के लिए कर्मचारियों को नियुक्त करते हैं। इस तरह, कार्य समानांतर में किए जा सकते हैं—अर्थात एक कार्य को चलाने के लिए दूसरे को रोकना नहीं पड़ता। विशिष्ट कार्यों के लिए अधिक कर्मचारियों की नियुक्ति वैसे ही है जैसे ऑपरेशंस को पूरा करने के लिए कई प्रोसेसर का उपयोग करना। उदाहरण के लिए, कंप्यूटर विज़न प्रोजेक्ट्स काफ़ी मांग वाले होते हैं क्योंकि आमतौर पर आपको बहुत-सी इमेज डेटा को प्रोसेस करना पड़ता है, जो समय-साध्य है: इस प्रक्रिया को तेज़ करने के लिए, आप कई इमेजेज़ को समानांतर में प्रोसेस कर सकते हैं।
इस प्रकार, हम कह सकते हैं कि multiprocessing प्रोग्राम्स को अधिक कुशल बनाने में उपयोगी है, जिसमें टास्क्स को अलग-अलग प्रोसेसर्स में बाँटकर सौंपा जाता है। Python का multiprocessing मॉड्यूल इसे और सरल बना देता है, क्योंकि यह उच्च-स्तरीय टूल के रूप में अलग-अलग प्रॉसेसेज़ को टास्क्स सौंपकर आपके प्रोग्राम्स की दक्षता बढ़ाता है।
Python के Multiprocessing मॉड्यूल की बुनियादी बातें
हमने बताया था कि Python प्रॉसेस का जीवनचक्र तीन चरणों से बना होता है: नया प्रॉसेस, चल रहा प्रॉसेस, और समाप्त प्रॉसेस। इस खंड में हम जीवनचक्र के प्रत्येक चरण में गहराई से जाएँगे और कोडित उदाहरण देंगे। साथ चलने के लिए आप हमारा DataLab वर्कबुक खोल सकते हैं।
नया प्रॉसेस
नए प्रॉसेस को उस प्रॉसेस के रूप में परिभाषित किया जा सकता है जो Process क्लास का एक इंस्टेंस बनाकर तैयार किया गया हो। जब हम Process ऑब्जेक्ट को किसी वैरिएबल को असाइन करते हैं, तो एक चाइल्ड प्रॉसेस स्पॉन होता है।
from multiprocessing import Process
# Create a new process
process = Process()संचालित द्वारा
इस समय हमारा प्रॉसेस इंस्टेंस कुछ नहीं कर रहा है क्योंकि हमने एक खाली Process ऑब्जेक्ट इनिशियलाइज़ किया है। हम अपनी Process ऑब्जेक्ट की कॉन्फ़िगरेशन बदलकर किसी विशेष फ़ंक्शन को निष्पादित करवा सकते हैं, इसके लिए उस फ़ंक्शन को जिसे हम किसी अलग प्रॉसेस में चलाना चाहते हैं, क्लास के target पैरामीटर को पास करना होगा।
# Create a new process with a specified function to execute.defexample_function():pass
new_process = Process(target=example_function)संचालित द्वारा
यदि हमारे target फ़ंक्शन के पैरामीटर्स भी हों, तो हम उन्हें Process ऑब्जेक्ट के args पैरामीटर को ट्यूपल के रूप में पास कर देंगे।
यह क्रिया परदे के पीछे Process इंस्टेंस की run() मेथड कॉल करके प्रॉसेस की गतिविधि शुरू करती है। run() मेथड Process इंस्टेंस के target पैरामीटर में निर्दिष्ट कस्टम फ़ंक्शन (यदि दिया गया हो) को कॉल करने के लिए भी ज़िम्मेदार होती है।
याद करें कि ट्यूटोरियल की शुरुआत में हमने कहा था कि प्रत्येक प्रॉसेस में कम से कम एक थ्रेड होता है जिसे main thread कहा जाता है—यह डिफ़ॉल्ट है। इसलिए, जब कोई चाइल्ड प्रॉसेस शुरू होता है, तो उस चाइल्ड प्रॉसेस के लिए मुख्य थ्रेड बनाया जाता है और शुरू होता है। चाइल्ड प्रॉसेस में हमारा सारा कोड निष्पादित करने की ज़िम्मेदारी मुख्य थ्रेड की होती है।
हम start() मेथड के लौटने से लेकर चाइल्ड प्रॉसेस के समाप्त होने तक अपने Process इंस्टेंस पर is_alive() मेथड का उपयोग करके जाँच सकते हैं कि हमारा प्रॉसेस इंस्टेंस सक्रिय है या नहीं।
नोट: यदि आप इसे नोटबुक में परख रहे हैं, तो चल रहे प्रॉसेस को पकड़ने के लिए is_alive() कॉल उसी सेल में होना चाहिए जिसमें start() मेथड को कॉल किया गया है।
# Run the new process
process.start()# Check process is running
process.is_alive()"""
True
"""संचालित द्वारा
यदि प्रॉसेस नहीं चल रहा होगा, तो is_alive() मेथड का कॉल False लौटाएगा।
समाप्त प्रॉसेस
जब run() फ़ंक्शन रिटर्न करता है या बाहर निकलता है, तो प्रॉसेस समाप्त हो जाता है: हमें आवश्यक रूप से कुछ भी स्पष्ट रूप से करने की ज़रूरत नहीं होती। दूसरे शब्दों में, आप अपेक्षा कर सकते हैं कि जिस target फ़ंक्शन के निर्देश आपने सेट किए हैं, उनके पूरा होने के बाद प्रॉसेस समाप्त हो जाएगा। इसलिए, is_alive() मेथड false लौटाएगा।
# Check process is terminated - should return false.
process.is_alive()"""
False
"""संचालित द्वारा
हालाँकि, यदि कोई अनहैंडल्ड exception आता है या कोई त्रुटि उठती है, तो प्रॉसेस समाप्त हो सकता है। उदाहरण के लिए, यदि जिस फ़ंक्शन को आपने target के रूप में सेट किया है उसमें कोई त्रुटि है, तो प्रॉसेस समाप्त हो जाएगा।
# Create a new process with a specific function that has an error# to execute with args.defexample(args):
split_args =list(args.split())# "name" variable is not in the function namespace - should raise errorreturn name
# New process
process = Process(target=example, args=("Hi",))# Running the new process
process.start()# Check process is running
process.is_alive()"""
True
Process Process-15:
Traceback (most recent call last):
File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
self.run()
"""संचालित द्वारा
Process ऑब्जेक्ट में terminate() और kill() मेथड भी होते हैं, जो उपयोगकर्ताओं को किसी प्रॉसेस को बलपूर्वक समाप्त करने में सक्षम बनाते हैं।
# Create a new process with a specific function to execute with args.defexample(args):
split_args =list(args.split())# "name" variable is not in the function namespace - should raise errorreturn split_args
# New process
process = Process(target=example, args=("Hi",))# Running the new process
process.start()if process.is_alive():
process.terminate()# You can also use process.kill()print("Process terminated forcefully")"""
Process terminated forcefully
"""संचालित द्वारा
यह ध्यान रखना महत्वपूर्ण है कि बलपूर्वक समाप्ति किसी प्रॉसेस को समाप्त करने का अनुशंसित तरीका नहीं है: इससे सभी खुले संसाधन सुरक्षित रूप से बंद न हों या आवश्यक प्रोग्राम स्टेट संग्रहीत न हो पाए। बेहतर समाधान यह है कि किसी process-safe boolean फ्लैग या इसी तरह के टूल के साथ नियंत्रित शटडाउन किया जाए।
Python Multiprocessing ट्यूटोरियल
अब जब आप multiprocessing की बुनियादी बातों को समझ चुके हैं, तो एक उदाहरण पर काम करते हैं ताकि यह दिखा सकें कि Python में concurrent प्रोग्रामिंग कैसे की जाती है।
जो फ़ंक्शन हम बनाएँगे, वह बस एक स्टेटमेंट प्रिंट करेगा, 1 सेकंड के लिए स्लीप करेगा, फिर एक और प्रिंट करेगा—फ़ंक्शंस के बारे में अधिक जानें इस Python functions ट्यूटोरियल में।
import time
defdo_something():print("I'm going to sleep")
time.sleep(1)print("I'm awake")संचालित द्वारा
पहला कदम एक नया प्रॉसेस बनाना है: हम दो बनाएँगे।
# Create new child process
process_1 = Process(target=do_something)
process_2 = Process(target=do_something)संचालित द्वारा
नोटबुक वातावरण में एक concurrent प्रोग्राम इस तरह दिखेगा:
%%time
# Starts both processes
process_1.start()
process_2.start()"""
I'm going to sleep
CPU times: user 810 µs, sys: 7.34 ms, total: 8.15 ms
Wall time: 6.04 ms
I'm going to sleep
"""संचालित द्वारा
प्रोग्राम के आउटपुट को देखते हुए, यह काफ़ी स्पष्ट है कि हमारे कोड में कहीं न कहीं कोई समस्या है। टाइमर हमारे पहले प्रॉसेस के बीच में प्रिंट हो रहा है, और दूसरी प्रिंट स्टेटमेंट प्रिंट नहीं हुई है।
ऐसा इसलिए होता है क्योंकि यहाँ तीन प्रॉसेसेज़ चल रहे हैं: मुख्य प्रॉसेस, और process_1 तथा process_2। समय को ट्रैक कर उसे प्रिंट करने वाला प्रॉसेस मुख्य प्रॉसेस है। मुख्य प्रॉसेस को समय प्रिंट करने से पहले इंतज़ार कराने के लिए, हमें दोनों प्रॉसेसेज़ को चलाने के बाद उन पर join() मेथड कॉल करनी होगी।
%%time
# Create new child process (Cannot run a process more than once)
new_process_1 = Process(target=do_something)
new_process_2 = Process(target=do_something)
# Starts both processes
new_process_1.start()
new_process_2.start()
new_process_1.join()
new_process_2.join()"""
I'm going to sleep
I'm going to sleep
I'm awake
I'm awake
CPU times: user 0 ns, sys: 14 ms, total: 14 ms
Wall time: 1.01 s
"""संचालित द्वारा
समस्या हल हो गई।
इस रन का वॉल टाइम पहले रन से थोड़ा अधिक था। हालाँकि, इस रन ने समय जानकारी लौटाने से पहले हमारे दोनों target फ़ंक्शंस के कॉल पूरे कर लिए। हम इसी तर्क को लागू करके और अधिक प्रॉसेसेज़ को भी concurrent रूप से चला सकते हैं।
Python Multiprocessing पर अंतिम विचार
इस ट्यूटोरियल में, आपने सीखा कि प्रोग्राम्स को concurrent रूप से चलाकर Python प्रोग्राम्स को अधिक कुशल कैसे बनाया जा सकता है। विशेष रूप से, आपने सीखा:
प्रॉसेसेज़ क्या होते हैं और आप उन्हें अपने कंप्यूटर में कैसे देख सकते हैं।
Python के multiprocessing और threading मॉड्यूल्स के बीच समानताएँ और अंतर।
multiprocessing मॉड्यूल की बुनियादी बातें और multiprocessing का उपयोग करके Python प्रोग्राम को concurrent रूप से कैसे चलाएँ।
Python में प्रोग्रामिंग के बारे में और सीखना चाहते हैं? हमारा Python Programmer करियर ट्रैक देखें। पूर्व प्रोग्रामिंग अनुभव आवश्यक नहीं है, और ट्रैक के अंत तक आप Python में सफलतापूर्वक सॉफ़्टवेयर विकसित करने, डेटा संसाधित करने, और उन्नत विश्लेषण करने के लिए आवश्यक कौशल हासिल कर लेंगे।
Python की स्टैंडर्ड लाइब्रेरी में कई बिल्ट-इन पैकेज शामिल होते हैं, जिनसे डेवलपर्स तुरंत भाषा के फ़ायदों का लाभ उठाना शुरू कर सकते हैं।
ऐसा ही एक पैकेज multiprocessing मॉड्यूल है, जो सिस्टम को एक साथ कई प्रॉसेस चलाने में सक्षम बनाता है। दूसरे शब्दों में, डेवलपर्स एप्लिकेशन को छोटे-छोटे थ्रेड्स में बाँट सकते हैं, जो उनकी Python कोड से स्वतंत्र रूप से चल सकते हैं। ये थ्रेड्स या प्रॉसेसेज़ फिर ऑपरेटिंग सिस्टम द्वारा प्रोसेसर को आवंटित किए जाते हैं, जिससे वे समानांतर में चल पाते हैं और आपके Python प्रोग्राम्स के परफ़ॉर्मेंस और दक्षता में सुधार होता है।
यदि थ्रेड्स, प्रॉसेसेज़, प्रोसेसर्स आदि शब्द आपको अपरिचित लग रहे हैं, तो चिंता न करें। इस लेख में हम प्रॉसेस की परिभाषा, थ्रेड्स से उनके अंतर, और
multiprocessingमॉड्यूल का उपयोग कैसे करें—इन सब पर चर्चा करेंगे।Python में प्रॉसेसेज़ क्या होते हैं?
प्रॉसेसेज़ और थ्रेड्स की अवधारणा को समझना बेहद उपयोगी है, ताकि आप बेहतर तरीके से समझ सकें कि ऑपरेटिंग सिस्टम अलग-अलग निष्पादन चरणों में प्रोग्राम्स को कैसे प्रबंधित करता है। एक प्रॉसेस मूलतः किसी कंप्यूटर प्रोग्राम का संदर्भ होता है। प्रत्येक प्रोग्राम का एक प्रॉसेस उससे संबद्ध होता है।
जैसे ही आप यह लेख पढ़ रहे हैं, बहुत संभव है कि आपके कंप्यूटर पर एक ही समय में कई प्रॉसेसेज़ चल रहे हों, भले ही आपने कुछ ही प्रोग्राम खोले हों—ऐसा इसलिए है क्योंकि अधिकतर ऑपरेटिंग सिस्टम बैकग्राउंड में कई टास्क चलाते रहते हैं। उदाहरण के लिए, इस समय आपके पास केवल तीन प्रोग्राम खुले हो सकते हैं, लेकिन आपका कंप्यूटर एक साथ 30 से अधिक सक्रिय प्रॉसेसेज़ चला रहा हो सकता है।
आपके कंप्यूटर पर वर्तमान में चल रहे सक्रिय प्रॉसेसेज़ कैसे देखें, यह आपके ऑपरेटिंग सिस्टम पर निर्भर करता है:
Python वास्तविक सिस्टम-स्तर के प्रॉसेसेज़ तक पहुँच प्रदान करता है। multiprocessing मॉड्यूल की
Processक्लास का एक इंस्टेंस बनाने से डेवलपर्स को Python के माध्यम से अंतर्निहित नेटिव प्रॉसेस को संदर्भित करने में सक्षम बनता है। जब कोई प्रॉसेस शुरू होता है, तो परदे के पीछे एक नया नेटिव प्रॉसेस बनता है। एक Python प्रॉसेस का जीवनचक्र तीन चरणों से बना होता है: नए प्रॉसेस की शुरुआत, चल रहा प्रॉसेस, और समाप्त हुआ प्रॉसेस—हम प्रत्येक चरण को कवर करेंगे।सभी प्रॉसेसेज़ एक या अधिक थ्रेड्स से मिलकर बने होते हैं। याद है, हमने कहा था कि “एक प्रॉसेस मूलतः कंप्यूटर प्रोग्राम का संदर्भ है”? दरअसल, प्रत्येक Python प्रोग्राम एक प्रॉसेस होता है, जिसमें एक डिफ़ॉल्ट थ्रेड होता है जिसे मुख्य थ्र्रेड (main thread) कहा जाता है। मुख्य थ्रेड आपके Python प्रोग्राम्स के भीतर दिए गए निर्देशों को निष्पादित करने का ज़िम्मेदार होता है। हालाँकि, यह याद रखना महत्वपूर्ण है कि प्रॉसेसेज़ और थ्रेड्स अलग-अलग होते हैं।
Multiprocessing बनाम Threading
और सटीक रूप से कहें तो, Python के इंटरप्रेटर का एक इंस्टेंस—वह टूल जो Python में लिखे गए कोड को कंप्यूटर द्वारा समझी जाने वाली भाषा में बदलता है—एक प्रॉसेस के बराबर होता है। एक प्रॉसेस में कम से कम एक थ्रेड होता है, जिसे Python में “main thread” कहा जाता है, हालाँकि उसी प्रॉसेस के भीतर अन्य थ्रेड्स भी बनाए जा सकते हैं—प्रॉसेस के भीतर बनाए गए सभी अन्य थ्रेड्स उसी प्रॉसेस से संबद्ध होंगे।
थ्रेड आपके Python प्रोग्राम के निष्पादन का प्रतिनिधित्व करता है, और जैसे ही सभी non-background थ्रेड्स समाप्त हो जाते हैं, Python प्रॉसेस भी समाप्त हो जाता है।
Python में दो बेहद समान क्लासेज़ हैं जो हमें प्रॉसेसेज़ और थ्रेड्स पर अधिक नियंत्रण देती हैं: multiprocessing.Process और threading.Thread।
आइए इनकी समानताओं और भिन्नताओं की समीक्षा करें।
समानताएँ
Concurrency
Concurrency वह अवधारणा है जिसमें प्रोग्राम के अलग-अलग हिस्से क्रम से बाहर या आंशिक क्रम में निष्पादित हो सकते हैं, बिना अंतिम परिणाम पर असर डाले। दोनों क्लासेज़ मूल रूप से कन्करेंसी के लिए ही बनाई गई थीं।
Concurrency primitives के लिए समर्थन
multiprocessing.Process और threading.Thread क्लासेज़ समान concurrency primitives का समर्थन करती हैं—ऐसे टूल जो थ्रेड्स और प्रॉसेसेज़ का समन्वय और सिंक्रोनाइज़ेशन सक्षम करते हैं।
एकरूप API
एक बार जब आप multiprocessing.Process API समझ लेते हैं, तो आप वही ज्ञान threading.Thread API पर भी लागू कर सकते हैं, और इसके विपरीत। इन्हें जानबूझकर इसी तरह डिज़ाइन किया गया है।
अंतर
कार्य-क्षमता
हालाँकि इनकी APIs समान हैं, लेकिन प्रॉसेसेज़ और थ्रेड्स अलग-अलग होते हैं। प्रॉसेस, थ्रेड की तुलना में उच्च-स्तरीय अमूर्तन है: प्रॉसेस कंप्यूटर प्रोग्राम का संदर्भ होता है, और थ्रेड किसी प्रॉसेस का हिस्सा होता है। यह अंतर इन क्लासेज़ में निहित है। इसलिए, ये क्लासेज़ अंतर्निहित ऑपरेटिंग सिस्टम द्वारा प्रबंधित दो अलग-अलग नेटिव फ़ंक्शंस का प्रतिनिधित्व करती हैं।
साझा स्टेट तक पहुँच
दोनों क्लासेज़ साझा स्टेट तक अलग-अलग तरीके से पहुँचती हैं। चूँकि थ्रेड्स किसी प्रॉसेस से संबद्ध होते हैं, वे उसी प्रॉसेस के भीतर मेमोरी साझा कर सकते हैं। इसलिए, किसी नए थ्रेड में निष्पादित फ़ंक्शन को उसी प्रॉसेस के भीतर मौजूद डेटा और स्टेट तक पहुँच बनी रहती है। थ्रेड्स के बीच स्टेट साझा करने के इस तरीके को “shared memory” कहा जाता है और यह काफ़ी सीधा है। इसके विपरीत, प्रॉसेसेज़ के बीच स्टेट साझा करना अधिक जटिल है: स्टेट को सीरियलाइज़ करके प्रॉसेसेज़ के बीच भेजना पड़ता है। दूसरे शब्दों में, प्रॉसेसेज़ के पास अलग-अलग मेमोरी होती है, इसलिए वे shared memory का उपयोग नहीं करते। इसके बजाय, प्रॉसेसेज़ के बीच साझा करना “inter-process communication” नामक तकनीक से होता है, और Python में इसे करने के लिए multiprocessing.Pipe या multiprocessing.Queue जैसे स्पष्ट टूल्स की आवश्यकता होती है।
GIL
Python Global Interpreter Lock (GIL) एक लॉक है जो केवल एक थ्रेड को ही एक समय में Python इंटरप्रेटर पर नियंत्रण रखने देता है। कई थ्रेड्स GIL के अधीन होते हैं, जिसके कारण Python में multithreading करना अक्सर अच्छा विचार नहीं होता: CPython इंटरप्रेटर पर multithreading के माध्यम से सच्चा मल्टी-कोर निष्पादन समर्थित नहीं है। हालाँकि, प्रॉसेसेज़ GIL के अधीन नहीं होते, क्योंकि GIL प्रत्येक Python प्रॉसेस के भीतर तो होता है, लेकिन प्रॉसेसेज़ के पार नहीं।
उपयोग मामलों की दृष्टि से, जहाँ प्रोग्राम CPU-इंटेंसिव हो और उसे किसी IO या यूज़र इंटरैक्शन की आवश्यकता न हो, वहाँ multiprocessing प्रायः threading से बेहतर रहता है। वहीं, IO या नेटवर्क-बाउंड प्रोग्राम्स तथा ऐसे परिदृश्यों में जहाँ लक्ष्य एप्लिकेशन की प्रतिक्रियाशीलता बढ़ाना हो, threading बेहतर समाधान है।
Python Multiprocessing के लाभ
प्रोसेसर को एक उद्यमी की तरह समझें। जैसे-जैसे उद्यमी का व्यवसाय बढ़ता है, व्यवसाय की वृद्धि के साथ तालमेल बनाए रखने के लिए अधिक कार्यों का प्रबंधन करना पड़ता है। यदि उद्यमी यह तय करता है कि वह सारे कार्य अकेले ही करेगा (जैसे अकाउंटिंग, सेल्स, मार्केटिंग, इनोवेशन आदि), तो वह व्यवसाय की कुल दक्षता और प्रदर्शन को बाधित करने का जोखिम उठाता है, क्योंकि एक व्यक्ति एक समय में सीमित ही काम कर सकता है। उदाहरण के लिए, इनोवेशन कार्यों पर जाने से पहले उसे सेल्स कार्यों को रोकना पड़ेगा—इसे टास्क्स को “अनुक्रम में” चलाना कहा जाता है।
अधिकांश उद्यमी समझते हैं कि सब कुछ अकेले करना बुरा विचार है। नतीजतन, वे आमतौर पर बढ़ते कार्यभार को संतुलित करने के लिए अलग-अलग विभागों के लिए कर्मचारियों को नियुक्त करते हैं। इस तरह, कार्य समानांतर में किए जा सकते हैं—अर्थात एक कार्य को चलाने के लिए दूसरे को रोकना नहीं पड़ता। विशिष्ट कार्यों के लिए अधिक कर्मचारियों की नियुक्ति वैसे ही है जैसे ऑपरेशंस को पूरा करने के लिए कई प्रोसेसर का उपयोग करना। उदाहरण के लिए, कंप्यूटर विज़न प्रोजेक्ट्स काफ़ी मांग वाले होते हैं क्योंकि आमतौर पर आपको बहुत-सी इमेज डेटा को प्रोसेस करना पड़ता है, जो समय-साध्य है: इस प्रक्रिया को तेज़ करने के लिए, आप कई इमेजेज़ को समानांतर में प्रोसेस कर सकते हैं।
इस प्रकार, हम कह सकते हैं कि multiprocessing प्रोग्राम्स को अधिक कुशल बनाने में उपयोगी है, जिसमें टास्क्स को अलग-अलग प्रोसेसर्स में बाँटकर सौंपा जाता है। Python का multiprocessing मॉड्यूल इसे और सरल बना देता है, क्योंकि यह उच्च-स्तरीय टूल के रूप में अलग-अलग प्रॉसेसेज़ को टास्क्स सौंपकर आपके प्रोग्राम्स की दक्षता बढ़ाता है।
Python के Multiprocessing मॉड्यूल की बुनियादी बातें
हमने बताया था कि Python प्रॉसेस का जीवनचक्र तीन चरणों से बना होता है: नया प्रॉसेस, चल रहा प्रॉसेस, और समाप्त प्रॉसेस। इस खंड में हम जीवनचक्र के प्रत्येक चरण में गहराई से जाएँगे और कोडित उदाहरण देंगे। साथ चलने के लिए आप हमारा DataLab वर्कबुक खोल सकते हैं।
नया प्रॉसेस
नए प्रॉसेस को उस प्रॉसेस के रूप में परिभाषित किया जा सकता है जो
Processक्लास का एक इंस्टेंस बनाकर तैयार किया गया हो। जब हमProcessऑब्जेक्ट को किसी वैरिएबल को असाइन करते हैं, तो एक चाइल्ड प्रॉसेस स्पॉन होता है।इस समय हमारा प्रॉसेस इंस्टेंस कुछ नहीं कर रहा है क्योंकि हमने एक खाली Process ऑब्जेक्ट इनिशियलाइज़ किया है। हम अपनी Process ऑब्जेक्ट की कॉन्फ़िगरेशन बदलकर किसी विशेष फ़ंक्शन को निष्पादित करवा सकते हैं, इसके लिए उस फ़ंक्शन को जिसे हम किसी अलग प्रॉसेस में चलाना चाहते हैं, क्लास के target पैरामीटर को पास करना होगा।
यदि हमारे target फ़ंक्शन के पैरामीटर्स भी हों, तो हम उन्हें Process ऑब्जेक्ट के args पैरामीटर को ट्यूपल के रूप में पास कर देंगे।
टिप: Python में फ़ंक्शन लिखना सीखें इस इंटरैक्टिव कोर्स Writing Functions in Python के साथ।
ध्यान दें कि हमने केवल एक नया प्रॉसेस बनाया है, यह अभी चल नहीं रहा है।
आइए देखें, नया प्रॉसेस कैसे चलाएँ।
चल रहा प्रॉसेस
नए प्रॉसेस को चलाना काफ़ी सीधा है: बस प्रॉसेस इंस्टेंस की
start()मेथड कॉल करें।यह क्रिया परदे के पीछे Process इंस्टेंस की
run()मेथड कॉल करके प्रॉसेस की गतिविधि शुरू करती है।run()मेथड Process इंस्टेंस के target पैरामीटर में निर्दिष्ट कस्टम फ़ंक्शन (यदि दिया गया हो) को कॉल करने के लिए भी ज़िम्मेदार होती है।याद करें कि ट्यूटोरियल की शुरुआत में हमने कहा था कि प्रत्येक प्रॉसेस में कम से कम एक थ्रेड होता है जिसे main thread कहा जाता है—यह डिफ़ॉल्ट है। इसलिए, जब कोई चाइल्ड प्रॉसेस शुरू होता है, तो उस चाइल्ड प्रॉसेस के लिए मुख्य थ्रेड बनाया जाता है और शुरू होता है। चाइल्ड प्रॉसेस में हमारा सारा कोड निष्पादित करने की ज़िम्मेदारी मुख्य थ्रेड की होती है।
हम
start()मेथड के लौटने से लेकर चाइल्ड प्रॉसेस के समाप्त होने तक अपनेProcessइंस्टेंस परis_alive()मेथड का उपयोग करके जाँच सकते हैं कि हमारा प्रॉसेस इंस्टेंस सक्रिय है या नहीं।नोट: यदि आप इसे नोटबुक में परख रहे हैं, तो चल रहे प्रॉसेस को पकड़ने के लिए
is_alive()कॉल उसी सेल में होना चाहिए जिसमेंstart()मेथड को कॉल किया गया है।यदि प्रॉसेस नहीं चल रहा होगा, तो
is_alive()मेथड का कॉल False लौटाएगा।समाप्त प्रॉसेस
जब
run()फ़ंक्शन रिटर्न करता है या बाहर निकलता है, तो प्रॉसेस समाप्त हो जाता है: हमें आवश्यक रूप से कुछ भी स्पष्ट रूप से करने की ज़रूरत नहीं होती। दूसरे शब्दों में, आप अपेक्षा कर सकते हैं कि जिस target फ़ंक्शन के निर्देश आपने सेट किए हैं, उनके पूरा होने के बाद प्रॉसेस समाप्त हो जाएगा। इसलिए,is_alive()मेथड false लौटाएगा।हालाँकि, यदि कोई अनहैंडल्ड exception आता है या कोई त्रुटि उठती है, तो प्रॉसेस समाप्त हो सकता है। उदाहरण के लिए, यदि जिस फ़ंक्शन को आपने target के रूप में सेट किया है उसमें कोई त्रुटि है, तो प्रॉसेस समाप्त हो जाएगा।
Process ऑब्जेक्ट में
terminate()औरkill()मेथड भी होते हैं, जो उपयोगकर्ताओं को किसी प्रॉसेस को बलपूर्वक समाप्त करने में सक्षम बनाते हैं।यह ध्यान रखना महत्वपूर्ण है कि बलपूर्वक समाप्ति किसी प्रॉसेस को समाप्त करने का अनुशंसित तरीका नहीं है: इससे सभी खुले संसाधन सुरक्षित रूप से बंद न हों या आवश्यक प्रोग्राम स्टेट संग्रहीत न हो पाए। बेहतर समाधान यह है कि किसी process-safe boolean फ्लैग या इसी तरह के टूल के साथ नियंत्रित शटडाउन किया जाए।
Python Multiprocessing ट्यूटोरियल
अब जब आप multiprocessing की बुनियादी बातों को समझ चुके हैं, तो एक उदाहरण पर काम करते हैं ताकि यह दिखा सकें कि Python में concurrent प्रोग्रामिंग कैसे की जाती है।
जो फ़ंक्शन हम बनाएँगे, वह बस एक स्टेटमेंट प्रिंट करेगा, 1 सेकंड के लिए स्लीप करेगा, फिर एक और प्रिंट करेगा—फ़ंक्शंस के बारे में अधिक जानें इस Python functions ट्यूटोरियल में।
पहला कदम एक नया प्रॉसेस बनाना है: हम दो बनाएँगे।
नोटबुक वातावरण में एक concurrent प्रोग्राम इस तरह दिखेगा:
प्रोग्राम के आउटपुट को देखते हुए, यह काफ़ी स्पष्ट है कि हमारे कोड में कहीं न कहीं कोई समस्या है। टाइमर हमारे पहले प्रॉसेस के बीच में प्रिंट हो रहा है, और दूसरी प्रिंट स्टेटमेंट प्रिंट नहीं हुई है।
ऐसा इसलिए होता है क्योंकि यहाँ तीन प्रॉसेसेज़ चल रहे हैं: मुख्य प्रॉसेस, और
process_1तथाprocess_2। समय को ट्रैक कर उसे प्रिंट करने वाला प्रॉसेस मुख्य प्रॉसेस है। मुख्य प्रॉसेस को समय प्रिंट करने से पहले इंतज़ार कराने के लिए, हमें दोनों प्रॉसेसेज़ को चलाने के बाद उन परjoin()मेथड कॉल करनी होगी।नोट: यदि आप और जानने में रुचि रखते हैं, तो यह Stackoverflow चर्चा देखें।
आइए हमारा नया कोड स्निपेट देखें:
समस्या हल हो गई।
इस रन का वॉल टाइम पहले रन से थोड़ा अधिक था। हालाँकि, इस रन ने समय जानकारी लौटाने से पहले हमारे दोनों target फ़ंक्शंस के कॉल पूरे कर लिए। हम इसी तर्क को लागू करके और अधिक प्रॉसेसेज़ को भी concurrent रूप से चला सकते हैं।
Python Multiprocessing पर अंतिम विचार
इस ट्यूटोरियल में, आपने सीखा कि प्रोग्राम्स को concurrent रूप से चलाकर Python प्रोग्राम्स को अधिक कुशल कैसे बनाया जा सकता है। विशेष रूप से, आपने सीखा:
Python में प्रोग्रामिंग के बारे में और सीखना चाहते हैं? हमारा Python Programmer करियर ट्रैक देखें। पूर्व प्रोग्रामिंग अनुभव आवश्यक नहीं है, और ट्रैक के अंत तक आप Python में सफलतापूर्वक सॉफ़्टवेयर विकसित करने, डेटा संसाधित करने, और उन्नत विश्लेषण करने के लिए आवश्यक कौशल हासिल कर लेंगे।