Threading आपको अपनी प्रक्रिया के अलग-अलग भागों को साथ-साथ चलाने देती है (स्रोत: RealPython)। ये अलग-अलग भाग आमतौर पर स्वतंत्र होते हैं और उसी प्रक्रिया से संबंधित अलग-अलग निष्पादन इकाइयाँ होती हैं। प्रक्रिया दरअसल एक चल रहा प्रोग्राम है, जिसके भीतर ऐसी इकाइयाँ हो सकती हैं जो समवर्ती रूप से चलें। उदाहरण के लिए, एक वेब-ब्राउज़र एक प्रक्रिया हो सकता है; एक ऐसा अनुप्रयोग जो एक साथ कई कैमरे चलाता है, वह एक प्रक्रिया हो सकता है; एक वीडियो गेम भी प्रक्रिया का एक और उदाहरण है।
किसी प्रक्रिया के भीतर बहु-थ्रेडिंग (multi-threading) की धारणा आती है, जहाँ कई थ्रेड एक साझा लक्ष्य हासिल करने के लिए साथ काम करते हैं। थ्रेड्स का उपयोग करने का सबसे अहम लाभ यह है कि यह आपको प्रोग्राम को समानांतर में चलाने की सुविधा देता है।
आइए एक उदाहरण के साथ थ्रेडिंग की अवधारणा समझें। मान लीजिए आपके पास एक अनुप्रयोग है जो मॉल की पार्किंग में प्रवेश और निकास करने वाली कारों की गिनती करता है। आपके यंत्र में कई कैमरे हैं जो प्रवेश और निकास की निगरानी करते हैं और एक केंद्रीय डिवाइस से जुड़े हैं। प्रत्येक कैमरे के पास कारों के प्रवाह पर नज़र रखने के लिए एक एल्गोरिदम होगा, जो उसी प्रक्रिया या प्रोग्राम का हिस्सा होगा। हालांकि, प्रत्येक कैमरा और उस पर चल रहा एल्गोरिदम एक अलग थ्रेड का हिस्सा हो सकता है। इतना ही नहीं, कैमरे से पढ़े जा रहे फ़्रेम और उन्हीं फ़्रेमों पर भविष्यवाणी करने वाला एल्गोरिदम भी दो अलग-अलग थ्रेड हो सकते हैं।
एक और उदाहरण वीडियो गेम का हो सकता है जिसमें प्रक्रिया को ग्राफ़िक्स, उपयोगकर्ता अंत:क्रिया, और नेटवर्किंग (मल्टीप्लेयर खेलते समय) जैसे कार्यों को समानांतर में चलाना पड़ता है, क्योंकि उसे हर समय प्रत्युत्तरशील रहना होता है। इसे हासिल करने के लिए यह बहु-थ्रेडिंग के सिद्धांत का उपयोग करता है, जहाँ प्रत्येक थ्रेड स्वतंत्र और विशिष्ट कार्य चलाने के लिए ज़िम्मेदार होगा।
एक थ्रेड का अपना निष्पादन प्रवाह होता है, जिसका अर्थ है कि प्रक्रिया में एक ही समय पर कई चीज़ें घट रही होती हैं।
यह जानना महत्वपूर्ण है कि प्रत्येक प्रक्रिया में कम से कम एक थ्रेड होता ही है, जिसे main thread कहा जाता है। यदि आपके प्रोग्राम में कोई अतिरिक्त थ्रेड परिभाषित नहीं है, तब भी इसमें कम से कम मुख्य थ्रेड होगा, यानी स्वयं प्रक्रिया।
यदि आप ऊपर दिए गए चित्र को देखें, तो वहाँ एक प्रक्रिया है जिसमें ग्लोबल वेरिएबल्स के साथ मुख्य थ्रेड और तीन अलग-अलग थ्रेड t1, t2, और t3 हैं। प्रत्येक थ्रेड के अपने स्थानीय वेरिएबल्स और कोड का निष्पादन प्रवाह होता है। थ्रेड आम तौर पर प्रक्रिया के संसाधनों को साझा करते हैं; जैसे मुख्य थ्रेड के लिए उपलब्ध संसाधन t1, t2 और t3 के बीच भी साझा होंगे। सभी थ्रेड्स को ग्लोबल वेरिएबल्स तक पहुँच होगी, जबकि उनके अपने स्थानीय वेरिएबल्स भी होंगे।
थ्रेड्स के अलग-अलग प्रकार होते हैं:
कर्नेल थ्रेड
यूज़र थ्रेड
कर्नेल और यूज़र थ्रेड का संयोजन
अब आइए आपके प्रोग्राम में थ्रेडिंग के कुछ फायदे देखें:
मल्टी-थ्रेडिंग, यदि कई CPU उपलब्ध हों, तो प्रोग्राम के निष्पादन को तेज़ कर सकती है।
यह I/O ऑपरेशनों के दौरान, कई थ्रेड्स या मुख्य थ्रेड के साथ एक थ्रेड की मदद से, अन्य कार्य करने देती है। उदाहरण के लिए, कैमरा फ़्रेम पढ़ने की गति और एल्गोरिदम द्वारा उन पर अनुमान लगाने का काम अलग-अलग थ्रेड्स संभालेंगे। इसलिए, एल्गोरिदम को फ़्रेम के इनपुट होने का इंतज़ार नहीं करना पड़ेगा, और फ़्रेम पढ़ने वाले हिस्से को अगले फ़्रेम को पढ़ने के लिए एल्गोरिदम के पूरा होने का इंतज़ार नहीं करना पड़ेगा।
उसी प्रक्रिया के भीतर थ्रेड्स, मुख्य थ्रेड की मेमोरी और संसाधन साझा कर सकते हैं।
थ्रेडिंग की चुनौतियाँ
ध्यान रखें कि Python का सामान्य उपयोग CPython इम्प्लीमेंटेशन पर आधारित है, जो एक समय में केवल एक थ्रेड को चलने देता है, इसलिए; थ्रेडिंग सभी कार्यों को तेज़ नहीं करेगी। इसका मूल कारण Global Interpreter Lock (GIL) है।
यदि आप GIL के बारे में सीखना चाहते हैं, तो यह ट्यूटोरियल देखें।
यदि आप CPU-इंटेंसिव कार्यों को तेज़ करना चाहते हैं, तो थ्रेडिंग आपके लिए सर्वोत्तम समाधान नहीं हो सकता। ऐसे मामलों में, मल्टी-प्रोसेसिंग उपयोगी मानी जाती है।
कई थ्रेड्स वाले प्रोग्राम को चलाने के लिए थ्रेड्स के बीच तेज़ी से स्विच करना पड़ता है; इसलिए जहाँ बहुत से थ्रेड हों वहाँ शेड्यूलिंग का ध्यान रखना आवश्यक है।
संसाधन साझा करना भी समस्या बन सकता है, क्योंकि सभी थ्रेड्स ग्लोबल वेरिएबल्स की समान मेमोरी और संसाधन साझा करते हैं। इसलिए, एक थ्रेड में किए गए ऑपरेशन दूसरे थ्रेड के लिए मेमोरी त्रुटि का कारण बन सकते हैं, या दूसरे थ्रेड को अपने कार्य के लिए पर्याप्त मेमोरी न मिले।
Python में Threading
Python में threading नाम का एक बिल्ट-इन मॉड्यूल है, जिसे सीधे इम्पोर्ट किया जा सकता है।
क्योंकि Python में लगभग हर चीज़ एक ऑब्जेक्ट के रूप में होती है, थ्रेडिंग भी Python में एक ऑब्जेक्ट है। एक थ्रेड सक्षम है:
डेटा रखने में,
डिक्शनरी, लिस्ट, सेट आदि जैसे डेटा स्ट्रक्चर में संग्रहीत होने में,
किसी फ़ंक्शन को पैरामीटर के रूप में पास होने में।
एक थ्रेड को एक प्रक्रिया की तरह भी चलाया जा सकता है।
Python में एक थ्रेड के विभिन्न स्टेट हो सकते हैं, जैसे:
Wait,
Locked.
अब सीखते हैं कि आप Python में थ्रेडिंग को कैसे इम्प्लीमेंट कर सकते हैं।
Python3 में Thread मॉड्यूल
ध्यान दें कि Python3, Python2.7 में मौजूद thread मॉड्यूल के साथ बैकवर्ड-कम्पैटिबल है। Python3 में इसे _thread मॉड्यूल के रूप में इम्पोर्ट किया जा सकता है। तो, एक उदाहरण के माध्यम से _thread मॉड्यूल समझते हैं।
import _thread #thread module imported
import time #time module
आइए thread_delay नाम का एक फ़ंक्शन परिभाषित करें, जो इनपुट के रूप में दो पैरामीटर लेगा, यानी थ्रेड का नाम और देरी। इस फ़ंक्शन के भीतर, आप:
काउंटर को शून्य से परिभाषित करेंगे,
फिर while लूप लगाएंगे जो तीन बार चलेगा,
while लूप के अंदर, आप देरी जोड़ने के लिए time.sleep() का उपयोग करेंगे; यह देरी थ्रेड के निष्पादन को समझने में सहायक होगी। यह देरी सेकंड में होगी,
फिर आप काउंटर को 1 से बढ़ाएँगे,
यह देखने के लिए कि आपका थ्रेड निष्पादन सुचारू रूप से हो रहा है, आप थ्रेड का नाम और थ्रेड के चलने का समय प्रिंट करेंगे।
अब ऊपर दिए गए फ़ंक्शन पर थ्रेड की कार्यक्षमता जोड़ने या इस फ़ंक्शन को थ्रेड में चलाने के लिए, आप _thread module के अंदर मौजूद start_new_thread मेथड का उपयोग करेंगे।
आइए start_new_thread मेथड का डॉकस्ट्रिंग देखें।
?_thread.start_new_thread
आइए फ़ंक्शन thread_delay को बिना कोष्ठक के और दो आर्गुमेंट्स—थ्रेड का नाम और देरी (क्योंकि थ्रेड का निष्पादन बहुत तेज़ है, इसलिए विज़ुअलाइज़ करने के लिए)—पास करें।
ऊपर दिए गए आउटपुट से आप देख सकते हैं कि थ्रेड t1 पहले निष्पादित होना शुरू होता है। इस बीच, थ्रेड t2 3-सेकंड की देरी के कारण प्रतीक्षा करता है, और जैसे ही वह देरी समाप्त होती है, थ्रेड t2 निष्पादित होता है; साथ ही, थ्रेड t1 की देरी केवल 1 सेकंड है।
अब t2 की देरी को 5 सेकंड कर दें, जिससे t2 तब शुरू होगा जब तक t1 का निष्पादन पूरा न हो जाए, क्योंकि t1 को अपना निष्पादन पूरा करने में केवल 3 सेकंड लगेंगे।
आप join मेथड का भी उपयोग करेंगे, जिसका अर्थ है कि तब तक प्रतीक्षा करें जब तक सभी थ्रेड्स का निष्पादन पूरा न हो जाए। इसलिए join मेथड के बाद आपने जो भी कोड लिखा होगा, वह तभी निष्पादित होगा जब ये थ्रेड समाप्त हो जाएँगे।
?t1.join
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Thread execution is complete!
Exception in thread Thread-9:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_square() argument after * must be an iterable, not int
Exception in thread Thread-8:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_cube() argument after * must be an iterable, not int
रुकिए, क्या? जैसे ही आपने start मेथड चलाया, त्रुटि आ गई। ऐसा इसलिए हुआ क्योंकि args पैरामीटर एक tuple अपेक्षित करता है, और चूंकि volume_cube और volume_square केवल एक पैरामीटर लेते हैं, इसलिए आपको फ़ंक्शनों के पहले आर्गुमेंट को देने के बाद args पैरामीटर में कॉमा लगाना होगा।
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Volume of Cube: 8
Volume of Square: 9
Thread execution is complete!
अब थ्रेडिंग को एक सब-क्लास के रूप में उपयोग करना सीखते हैं।
सब-क्लास के रूप में Threading
इस खंड में आप सीखेंगे कि threading मॉड्यूल के भीतर उपलब्ध Thread क्लास से एक सब-क्लास कैसे बनाया जाए और फिर उस क्लास से t1 और t2 थ्रेड इंस्टैंस कैसे बनाए जाएँ।
Starting Thread: t1
Starting Thread: t2
t1 --------> 1588089083.4879806
t1 --------> 1588089084.489222
t1 --------> 1588089085.4909666
Execution of Thread: t1 is complete!
t2 --------> 1588089085.4979634
t2 --------> 1588089088.499365
t2 --------> 1588089091.5012596
Execution of Thread: t2 is complete!
Thread execution is complete!
अब ऊपर दिए गए कोड को चरण-दर-चरण समझते हैं:
आपने DataCampThread नामक एक क्लास परिभाषित की और क्लास आर्गुमेंट में threading.Thread क्लास पास की। ऐसा इसलिए किया क्योंकि आप threading मॉड्यूल से एक सब-क्लास बनाना चाहते हैं। ऐसा करने से आप threading.Thread क्लास में उपलब्ध सभी मेथड्स का उपयोग कर सकते हैं, जो कि इनहेरिटेंस की अवधारणा जैसा है।
इसके बाद आपने DataCampThread क्लास का init मेथड परिभाषित किया और फ़ंक्शन के साथ देरी (delay) पास की। init एक कंस्ट्रक्टर है; जैसे आपने threading.Thread क्लास को पैरामीटर दिए थे, यहाँ आप उन्हें DataCampThread क्लास को देंगे।
फिर आपने मुख्य threading.Thread क्लास के init मेथड को इनिशियलाइज़ किया और name तथा delay को असाइन किया।
अगला, आप run मेथड परिभाषित करते हैं; याद करें यहाँ आपने start मेथड परिभाषित किया था, जो अंदरूनी तौर पर run मेथड को कॉल करता है। चूँकि आपने मुख्य क्लास को इनहेरिट किया है, आपको run मेथड में बदलाव करने की अनुमति है। इसलिए जब आप start मेथड को कॉल करेंगे, तो run मेथड में किए गए संशोधन अब परिलक्षित होंगे।
निष्कर्ष
ट्यूटोरियल पूरा करने के लिए बधाई।
यह ट्यूटोरियल Python में थ्रेडिंग का एक बुनियादी परिचय था। हालांकि, थ्रेडिंग बहुत व्यापक और महत्वपूर्ण विषय है, और कुछ अवधारणाएँ शायद अनछुई रह गई हों। निःसंकोच active count, current thread आदि जैसे अन्य थ्रेडिंग मेथड्स भी देखें।
Python के साथ डेटा विश्लेषण की बुनियादी बातें सिर्फ चार घंटे में सीखें। यह ऑनलाइन पाठ्यक्रम Python इंटरफ़ेस का परिचय देगा और लोकप्रिय पैकेजों का अन्वेषण करेगा।