Ang `threading` Module sa Python
Ang artikulong ito ay nagpapaliwanag tungkol sa threading module sa Python.
YouTube Video
Ang threading Module sa Python
Ang threading module sa Python ay isang standard library na sumusuporta sa multithreading programming. Ang paggamit ng threads ay nagpapahintulot sa maraming proseso na tumakbo nang sabay-sabay, na maaaring magpabuti ng performance ng mga programa, lalo na sa mga sitwasyon na may kaugnayan sa blocking operations tulad ng I/O waits. Dahil sa Global Interpreter Lock (GIL) ng Python, limitado ang bisa ng multithreading para sa CPU-bound operations, ngunit ito ay mahusay para sa I/O-bound operations.
Ang sumusunod na mga seksyon ay nagpapaliwanag ng mga pangunahing kaalaman sa paggamit ng threading module at kung paano kontrolin ang mga thread.
Pangunahing Paggamit ng Threads
Paglikha at Pagpapatakbo ng Threads
Upang lumikha ng thread at maisagawa ang sabayang pagproseso, gamitin ang threading.Thread class. Tukuyin ang target na function upang lumikha ng thread at isagawa ito.
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")- Sa halimbawang ito, isinasagawa ang
workerfunction sa isang hiwalay na thread, samantalang ang main thread ay patuloy na tumatakbo. Sa pamamagitan ng pagtawag sajoin()method, hinihintay ng main thread na matapos ang sub-thread.
Pagbibigay ng Pangalan sa mga Thread
Mas pinadadali ng pagbibigay ng makabuluhang pangalan sa mga thread ang pag-log at pag-debug. Maaari mo itong itakda gamit ang name na argument.
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()- Nagbabalik ang
threading.enumerate()ng listahan ng mga kasalukuyang thread, na kapaki-pakinabang para sa pag-debug at pagmamanman ng estado.
Pagmamana ng Thread Class
Kung nais mong i-customize ang thread-executing na klase, maaari kang magtukoy ng bagong klase sa pamamagitan ng pagmamana ng threading.Thread class.
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)- Sa halimbawang ito, ang
run()method ay inoverride para tukuyin ang asal ng thread, kaya maaaring magkaroon ng sariling data ang bawat thread. Kapaki-pakinabang ito kapag ang mga thread ay gumagawa ng komplikadong proseso o kapag nais mo na may sariling independent na data ang bawat thread.
Pag-synchronize sa Pagitan ng Mga Threads
Kapag maraming threads ang sabay na uma-access sa shared resources, maaaring mangyari ang data races. Upang maiwasan ito, ang threading module ay nagbibigay ng ilang mekanismo ng pagsasabay.
Lock (Lock)
Ang Lock object ay ginagamit upang magpatupad ng eksklusibong kontrol sa mga resources sa pagitan ng threads. Habang naka-lock ang isang thread sa resource, hindi maaaring ma-access ng ibang threads ang resource na iyon.
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}")- Sa halimbawang ito, limang threads ang uma-access sa shared resource, ngunit ginagamit ang
Lockupang maiwasan ang sabay-sabay na pagbabago ng data ng maraming threads.
Reentrant Lock (RLock)
Kung kailangan ng isang thread na mag-acquire ng lock ng maraming beses, gumamit ng RLock (reentrant lock). Kapaki-pakinabang ito para sa mga recursive na tawag o sa mga tawag sa library na posibleng mag-acquire ng locks sa magkakaibang tawag.
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)- Sa paggamit ng
RLock, maaaring mag-reacquire ng lock ang parehong thread na hawak na nito, na nakakatulong maiwasan ang deadlock sa nested na pag-acquire ng lock.
Condition (Condition)
Condition ay ginagamit para maghintay ang mga thread hanggang ang isang tiyak na kondisyon ay matupad. Kapag natugunan ng isang thread ang isang kondisyon, maaari mong tawagin ang notify() upang i-notify ang ibang thread, o notify_all() upang i-notify ang lahat ng naghihintay na threads.
Narito sa ibaba ang halimbawa ng producer at consumer na gumagamit ng 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()- Gumagamit ang code na ito ng
Conditionkung saan inaabisuhan ng producer kapag may nadagdag na data, at naghihintay ang consumer sa notification na iyon bago kunin ang data, upang magawa ang tamang pagsabay.
Pag-Daemonize ng Thread
Ang mga daemon thread ay sapilitang tinatapos kapag natapos ang pangunahing thread. Habang ang mga normal na thread ay kailangang maghintay bago matapos, ang mga daemon thread ay awtomatikong natatapos.
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")- Sa halimbawang ito, ang
workerthread ay na-daemonize, kaya't ito ay sapilitang tinatapos kapag natapos ang pangunahing thread.
Pamamahala ng Thread gamit ang ThreadPoolExecutor
Maliban sa threading module, maaari mong gamitin ang ThreadPoolExecutor mula sa concurrent.futures module upang pamahalaan ang isang pool ng mga thread at magpatupad ng mga gawain nang sabay-sabay.
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())- Ang
ThreadPoolExecutoray lumilikha ng thread pool at mahusay na nagpoproseso ng mga gawain. Itakda ang bilang ng mga thread na tatakbo nang sabay-sabay gamit angmax_workers.
Komunikasyon ng Mga Kaganapan sa Pagitan ng Mga Thread
Gamit ang threading.Event, maaari kang magtakda ng mga flag sa pagitan ng mga thread upang ipaalam sa ibang mga thread ang paglitaw ng isang kaganapan.
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- Ipinapakita ng code na ito ang mekanismo kung saan naghihintay ang worker thread sa signal ng
Eventat muling magpapatuloy ang proseso kapag tinawag ng main thread angevent.set().
Exception Handling at Pagwawakas ng Thread sa mga Thread
Kapag may naganap na eksepsiyon sa mga thread, hindi ito agad napapasa sa main thread, kaya kailangan ng pattern para mahuli at maibahagi ang mga exception.
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)- Sa pamamagitan ng paglalagay ng mga exception sa isang
Queueat pagkuha ng mga ito sa main thread, mapagkakatiwalaang mong matukoy ang mga pagkabigo. Kung gagamitin mo angconcurrent.futures.ThreadPoolExecutor, muling itinatapon ang exceptions gamit angfuture.result(), kaya mas madali itong hawakan.
Ang GIL (Global Interpreter Lock) at ang mga Epekto Nito
Dahil sa mekanismo ng GIL (Global Interpreter Lock) sa CPython, hindi talaga sabay-sabay na tumatakbo ang maraming Python bytecode sa iisang proseso. Para sa mga gawain na nangangailangan ng matinding computation sa CPU, inirerekomendang gumamit ng multiprocessing. Sa kabilang banda, para sa mga I/O-bound na gawain tulad ng pagbabasa ng file o komunikasyon sa network, epektibo ang paggamit ng threading.
Buod
Gamit ang threading module ng Python, maaari kang magpatupad ng mga multithreaded na programa at magpatakbo ng maraming proseso nang sabay-sabay. Gamit ang mga mekanismo ng pagsasabay tulad ng Lock at Condition, maaari mong ligtas na ma-access ang mga pinagbahagiang mapagkukunan at maisagawa ang kumplikadong pagsasabay. Bukod dito, gamit ang daemon threads o ThreadPoolExecutor, ang pamamahala ng thread at mahusay na parallel processing ay nagiging mas madali.
Maaari mong sundan ang artikulo sa itaas gamit ang Visual Studio Code sa aming YouTube channel. Paki-check din ang aming YouTube channel.