Python में `threading` मॉड्यूल
यह लेख Python में threading मॉड्यूल को समझाता है।
YouTube Video
Python में threading मॉड्यूल
Python में threading मॉड्यूल एक मानक लाइब्रेरी है जो मल्टिथ्रेडिंग प्रोग्रामिंग का समर्थन करती है। थ्रेड्स का उपयोग करने से कई प्रक्रियाएं एक साथ चल सकती हैं, जिससे प्रोग्राम की कार्यक्षमता में सुधार हो सकता है, विशेष रूप से ब्लॉकिंग संचालन जैसे कि I/O प्रतीक्षा के मामलों में। पायथन के ग्लोबल इंटरप्रेटर लॉक (GIL) के कारण, मल्टीथ्रेडिंग की प्रभावशीलता CPU-आधारित संचालन के लिए सीमित है, लेकिन यह I/O-आधारित संचालन के लिए कुशलतापूर्वक काम करती है।
आगामी अनुभाग threading मॉड्यूल का उपयोग करने की मूल बातें और थ्रेड को कैसे नियंत्रित करें, समझाते हैं।
थ्रेड्स का बुनियादी उपयोग
थ्रेड्स बनाना और चलाना
एक थ्रेड बनाने और समवर्ती प्रसंस्करण करने के लिए, threading.Thread कक्षा का उपयोग करें। एक थ्रेड बनाने के लिए लक्षित फ़ंक्शन निर्दिष्ट करें और उस थ्रेड को निष्पादित करें।
1import threading
2import time
3
4# Function to be executed in a thread
5def worker():
6 print("Worker thread started")
7 time.sleep(2)
8 print("Worker thread finished")
9
10# Create and start the thread
11thread = threading.Thread(target=worker)
12thread.start()
13
14# Processing in the main thread
15print("Main thread continues to run")
16
17# Wait for the thread to finish
18thread.join()
19print("Main thread finished")- इस उदाहरण में,
workerफ़ंक्शन को एक अलग थ्रेड में निष्पादित किया जाता है, जबकि मुख्य थ्रेड काम करता रहता है।join()मेथड को कॉल करके, मुख्य थ्रेड उप-थ्रेड के पूरा होने का इंतजार करता है।
थ्रेड्स को नाम देना
थ्रेड्स को अर्थपूर्ण नाम देने से लॉगिंग और डिबगिंग आसान हो जाती है। आप इसे name आर्ग्युमेंट के साथ निर्दिष्ट कर सकते हैं।
1import threading
2import time
3
4# Function to be executed in a thread
5def worker():
6 print("Worker thread started")
7 time.sleep(2)
8 print("Worker thread finished")
9
10t = threading.Thread(
11 target=worker,
12 args=("named-worker", 0.3),
13 name="MyWorkerThread"
14)
15
16t.start()
17
18print("Active threads:", threading.active_count())
19for th in threading.enumerate():
20 print(" -", th.name)
21
22t.join()threading.enumerate()वर्तमान थ्रेड्स की सूची लौटाता है, जो डिबगिंग और स्थिति की निगरानी के लिए उपयोगी है।
थ्रेड कक्षा को विरासत में लेना
यदि आप थ्रेड-निष्पादन कक्षा को अनुकूलित करना चाहते हैं, तो आप threading.Thread कक्षा को विरासत में लेकर एक नई कक्षा परिभाषित कर सकते हैं।
1import threading
2import time
3
4# Inherit from the Thread class
5class WorkerThread(threading.Thread):
6 def __init__(self, name, delay, repeat=3):
7 super().__init__(name=name)
8 self.delay = delay
9 self.repeat = repeat
10 self.results = []
11
12 def run(self):
13 for i in range(self.repeat):
14 msg = f"{self.name} step {i+1}"
15 print(msg)
16 self.results.append(msg)
17 time.sleep(self.delay)
18
19# Create and start the threads
20t1 = WorkerThread("Worker-A", delay=0.4, repeat=3)
21t2 = WorkerThread("Worker-B", delay=0.2, repeat=5)
22
23t1.start()
24t2.start()
25
26t1.join()
27t2.join()
28
29print("Results A:", t1.results)
30print("Results B:", t2.results)- इस उदाहरण में, थ्रेड के व्यवहार को परिभाषित करने के लिए
run()मेथड को ओवरराइड किया गया है, जिससे हर थ्रेड अपना खुद का डेटा रख सकता है। यह तब उपयोगी है जब थ्रेड्स जटिल प्रोसेसिंग करते हैं या जब आप चाहते हैं कि हर थ्रेड के पास अपना स्वतंत्र डेटा हो।
थ्रेड्स के बीच सिंक्रोनाइज़ेशन
जब कई थ्रेड्स एक साथ साझा संसाधनों तक पहुंचते हैं, तो डेटा रेस हो सकती हैं। इसे रोकने के लिए, threading मॉड्यूल कई सिंक्रोनाइजेशन प्रणालियां प्रदान करता है।
लॉक (Lock)
Lock ऑब्जेक्ट का उपयोग थ्रेड्स के बीच संसाधनों के अनन्य नियंत्रण को लागू करने के लिए किया जाता है। जब एक थ्रेड संसाधन को लॉक करता है, तो अन्य थ्रेड्स उस संसाधन तक पहुंच नहीं कर सकते।
1import threading
2
3lock = threading.Lock()
4shared_resource = 0
5
6def worker():
7 global shared_resource
8 with lock: # Acquire the lock
9 local_copy = shared_resource
10 local_copy += 1
11 shared_resource = local_copy
12
13threads = [threading.Thread(target=worker) for _ in range(5)]
14
15for t in threads:
16 t.start()
17
18for t in threads:
19 t.join()
20
21print(f"Final value of shared resource: {shared_resource}")- इस उदाहरण में, पांच थ्रेड्स एक साझा संसाधन तक पहुंचते हैं, लेकिन
Lockका उपयोग कई थ्रेड्स को डेटा को एक साथ संशोधित करने से रोकने के लिए किया जाता है।
री-एंट्रेंट लॉक (RLock)
अगर एक ही थ्रेड को कई बार लॉक प्राप्त करने की आवश्यकता है, तो RLock (री-एंट्रेंट लॉक) का उपयोग करें। यह पुनरावर्ती कॉल्स (recursive calls) या ऐसी लाइब्रेरी कॉल्स के लिए उपयोगी है जिनमें विभिन्न कॉल्स में लॉक प्राप्त करना पड़ सकता है।
1import threading
2
3rlock = threading.RLock()
4shared = []
5
6def outer():
7 with rlock:
8 shared.append("outer")
9 inner()
10
11def inner():
12 with rlock:
13 shared.append("inner")
14
15t = threading.Thread(target=outer)
16t.start()
17t.join()
18print(shared)RLockके साथ, वही थ्रेड जो पहले से लॉक प्राप्त कर चुका है, दोबारा लॉक प्राप्त कर सकता है, जिससे नेस्टेड लॉकिंग में डेडलॉक से बचाव होता है।
कंडीशन (Condition)
Condition का उपयोग थ्रेड को तब तक प्रतीक्षा करने के लिए किया जाता है जब तक कि एक विशेष शर्त पूरी नहीं हो जाती। जब एक थ्रेड किसी शर्त को पूरा करता है, तो आप notify() को कॉल करके दूसरे थ्रेड को सूचित कर सकते हैं, या notify_all() को कॉल करके सभी प्रतीक्षा कर रहे थ्रेड्स को सूचित कर सकते हैं।
नीचे एक उदाहरण है जिसमें प्रोड्यूसर और कंज्यूमर Condition का उपयोग कर रहे हैं।
1import threading
2
3condition = threading.Condition()
4shared_data = []
5
6def producer():
7 with condition:
8 shared_data.append(1)
9 print("Produced an item")
10 condition.notify() # Notify the consumer
11
12def consumer():
13 with condition:
14 condition.wait() # Wait until the condition is met
15
16 item = shared_data.pop(0)
17 print(f"Consumed an item: {item}")
18
19# Create the threads
20producer_thread = threading.Thread(target=producer)
21consumer_thread = threading.Thread(target=consumer)
22
23consumer_thread.start()
24producer_thread.start()
25
26producer_thread.join()
27consumer_thread.join()- यह कोड
Conditionका उपयोग करता है ताकि प्रोड्यूसर डेटा जोड़ने पर सूचित करे और कंज्यूमर उस सूचना का इंतजार करे, इससे दोनों के बीच समन्वय (सिंक्रोनाइज़ेशन) संभव होता है।
थ्रेड डेमोनाईज़ेशन
डेमोन थ्रेड्स को मुख्य थ्रेड के समाप्त होने पर जबरदस्ती समाप्त कर दिया जाता है। जहां सामान्य थ्रेड्स को समाप्त होने के लिए प्रतीक्षा करनी पड़ती है, वहां डेमोन थ्रेड्स स्वचालित रूप से समाप्त हो जाते हैं।
1import threading
2import time
3
4def worker():
5 while True:
6 print("Working...")
7 time.sleep(1)
8
9# Create a daemon thread
10thread = threading.Thread(target=worker)
11thread.daemon = True # Set as a daemon thread
12
13thread.start()
14
15# Processing in the main thread
16time.sleep(3)
17print("Main thread finished")- इस उदाहरण में,
workerथ्रेड को डेमोनाइज़ किया गया है, इसलिए यह मुख्य थ्रेड के समाप्त होने पर जबरदस्ती समाप्त हो जाता है।
ThreadPoolExecutor के साथ थ्रेड प्रबंधन
threading मॉड्यूल के अलावा, आप concurrent.futures मॉड्यूल का ThreadPoolExecutor का उपयोग करके थ्रेड पूल को प्रबंधित कर सकते हैं और कार्यों को समानांतर में निष्पादित कर सकते हैं।
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def worker(seconds):
5 print(f"Sleeping for {seconds} second(s)")
6 time.sleep(seconds)
7 return f"Finished sleeping for {seconds} second(s)"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(worker, i) for i in range(1, 4)]
11 for future in futures:
12 print(future.result())ThreadPoolExecutorएक थ्रेड पूल बनाता है और कार्यों को कुशलता से संसाधित करता है।max_workersके साथ एक साथ चलने वाले थ्रेड्स की संख्या निर्दिष्ट करें।
थ्रेड्स के बीच ईवेंट संचार
threading.Event का उपयोग करके, आप थ्रेड्स के बीच फ्लैग सेट कर सकते हैं ताकि अन्य थ्रेड्स को किसी घटना की घटना के बारे में सूचित किया जा सके।
1import threading
2import time
3
4event = threading.Event()
5
6def worker():
7 print("Waiting for event to be set")
8 event.wait() # Wait until the event is set
9
10 print("Event received, continuing work")
11
12thread = threading.Thread(target=worker)
13thread.start()
14
15time.sleep(2)
16print("Setting the event")
17event.set() # Set the event and notify the thread- यह कोड एक मेकेनिज्म दर्शाता है जिसमें वर्कर थ्रेड
Eventसिग्नल का इंतजार करता है और मुख्य थ्रेड केevent.set()कॉल करने पर प्रोसेसिंग फिर से शुरू करता है।
थ्रेड्स में अपवाद (एक्सेप्शन) हैंडलिंग और थ्रेड समाप्ति
जब थ्रेड्स में अपवाद (एक्सेप्शन) आते हैं तो वे सीधे मुख्य थ्रेड तक नहीं पहुँचते, इसलिए एक्सेप्शंस को कैप्चर और साझा करने के लिए एक पैटर्न की आवश्यकता होती है।
1import threading
2import queue
3
4def worker(err_q):
5 try:
6 raise ValueError("Something bad")
7 except Exception as e:
8 err_q.put(e)
9
10q = queue.Queue()
11t = threading.Thread(target=worker, args=(q,))
12t.start()
13t.join()
14if not q.empty():
15 exc = q.get()
16 print("Worker raised:", exc)Queueमें अपवादों को डालकर और उन्हें मुख्य थ्रेड में प्राप्त करके, आप विफलताओं का भरोसेमंद तरीके से पता लगा सकते हैं। अगर आपconcurrent.futures.ThreadPoolExecutorका उपयोग करते हैं, तो अपवादों (एक्सेप्शनों) कोfuture.result()के साथ पुनः फेंका जाता है, जिससे उन्हें संभालना आसान हो जाता है।
GIL (ग्लोबल इंटरप्रेटर लॉक) और इसके प्रभाव
CPython में GIL (ग्लोबल इंटरप्रेटर लॉक) के कारण, एक ही प्रक्रिया में कई पाइथन बाइटकोड्स वास्तव में एक साथ नहीं चलते हैं। CPU-गहन कार्यों (जैसे भारी गणनाएँ) के लिए, multiprocessing का उपयोग करने की सलाह दी जाती है। वहीं, फ़ाइल पढ़ने या नेटवर्क संचार जैसी I/O-बाउंड कार्यों के लिए, threading प्रभावी रूप से कार्य करता है।
सारांश
Python के threading मॉड्यूल का उपयोग करते हुए, आप मल्टिथ्रेडेड प्रोग्राम्स को लागू कर सकते हैं और कई प्रक्रियाओं को एक साथ निष्पादित कर सकते हैं। Lock और Condition जैसी सिंक्रोनाइजेशन मेकेनिज्म के साथ, आप साझा संसाधनों को सुरक्षित रूप से एक्सेस कर सकते हैं और जटिल सिंक्रोनाइजेशन कर सकते हैं। इसके अलावा, डेमोन थ्रेड्स या ThreadPoolExecutor का उपयोग करके, थ्रेड प्रबंधन और कुशल समानांतर प्रसंस्करण सरल हो जाता है।
आप हमारे YouTube चैनल पर Visual Studio Code का उपयोग करके ऊपर दिए गए लेख के साथ आगे बढ़ सकते हैं। कृपया YouTube चैनल को भी देखें।