Modul `threading` dalam Python
Artikel ini menerangkan modul threading dalam Python.
YouTube Video
Modul threading dalam Python
Modul threading dalam Python ialah pustaka standard yang menyokong pengaturcaraan multibenang. Dengan menggunakan thread, pelbagai proses dapat dijalankan secara serentak, yang boleh meningkatkan prestasi program, terutamanya dalam kes melibatkan operasi yang tersekat seperti menunggu I/O. Disebabkan oleh Global Interpreter Lock (GIL) dalam Python, kecekapan multithreading adalah terhad untuk operasi yang melibatkan CPU tetapi berfungsi dengan cekap untuk operasi yang melibatkan I/O.
Bahagian berikut menerangkan asas penggunaan modul threading dan cara mengawal benang.
Penggunaan Asas Thread
Membuat dan Menjalankan Thread
Untuk membuat thread dan melaksanakan pemprosesan serentak, gunakan kelas threading.Thread. Nyatakan fungsi sasaran untuk membuat thread dan melaksanakan thread tersebut.
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")- Dalam contoh ini, fungsi
workerdilaksanakan dalam thread yang berasingan, manakala thread utama terus beroperasi. Dengan memanggil kaedahjoin(), thread utama menunggu thread sub selesai.
Menamakan Benang (Threads)
Memberi nama yang bermakna kepada benang memudahkan pencatatan (log) dan penyahpepijatan (debugging). Anda boleh menentukannya dengan argumen 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()mengembalikan senarai benang semasa, yang berguna untuk penyahpepijatan dan pemantauan status.
Mewarisi Kelas Thread
Jika anda ingin menyesuaikan kelas pelaksana thread, anda boleh mentakrifkan kelas baru dengan mewarisi kelas 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)- Dalam contoh ini, kaedah
run()dioveride untuk mentakrifkan tingkah laku benang, membenarkan setiap benang mengekalkan data mereka sendiri. Ini berguna apabila benang melakukan pemprosesan yang kompleks atau apabila anda ingin setiap benang mempunyai data tersendiri yang bebas.
Penyelarasan antara Thread
Apabila pelbagai thread mengakses sumber yang dikongsi secara serentak, perlumbaan data mungkin berlaku. Untuk mengelakkan ini, modul threading menyediakan beberapa mekanisme penyegerakan.
Kunci (Lock)
Objek Lock digunakan untuk melaksanakan kawalan eksklusif ke atas sumber di antara thread. Semasa satu thread mengunci sumber, thread lain tidak boleh mengakses sumber itu.
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}")- Dalam contoh ini, lima thread mengakses sumber yang dikongsi, tetapi
Lockdigunakan untuk mengelakkan pelbagai thread daripada mengubah data secara serentak.
Kunci Boleh Masuk Semula (RLock)
Jika benang yang sama perlu memperoleh kunci beberapa kali, gunakan RLock (kunci boleh masuk semula). Ini berguna untuk panggilan berulang (rekursif) atau panggilan perpustakaan yang mungkin memperoleh kunci dalam panggilan yang berlainan.
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)- Dengan
RLock, benang yang sama boleh memperoleh semula kunci yang telah dipegangnya, yang membantu mencegah kebuntuan (deadlock) semasa pengambilan kunci bersarang.
Keadaan (Condition)
Condition digunakan untuk benang menunggu sehingga syarat tertentu dipenuhi. Apabila satu utas memenuhi satu syarat, anda boleh memanggil notify() untuk memaklumkan utas lain, atau notify_all() untuk memaklumkan semua utas yang sedang menunggu.
Di bawah adalah contoh pengeluar (producer) dan pengguna (consumer) menggunakan 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()- Kod ini menggunakan
Conditionsupaya pengeluar memberi notifikasi apabila data ditambah, dan pengguna menunggu notifikasi itu sebelum mengambil data, bagi mencapai penyegerakan.
Pendemonaan Benang
Benang daemon akan ditamatkan secara paksa apabila benang utama berakhir. Sementara benang biasa perlu menunggu untuk ditamatkan, benang daemon tamat secara automatik.
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")- Dalam contoh ini, benang
workerdidemonaisasi, jadi ia ditamatkan secara paksa apabila benang utama berakhir.
Pengurusan Benang dengan ThreadPoolExecutor
Selain modul threading, anda boleh menggunakan ThreadPoolExecutor dari modul concurrent.futures untuk mengurus kumpulan benang dan melaksanakan tugas secara serentak.
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())ThreadPoolExecutormencipta kumpulan benang dan memproses tugas dengan cekap. Nyatakan bilangan benang untuk dijalankan secara serentak denganmax_workers.
Komunikasi Acara Antara Benang
Dengan threading.Event, anda boleh menetapkan bendera antara benang untuk memaklumkan benang lain tentang berlakunya suatu acara.
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- Kod ini menunjukkan mekanisme di mana benang pekerja menunggu isyarat
Eventdan meneruskan pemprosesan apabila benang utama memanggilevent.set().
Pengendalian Pengecualian dan Penamatan Benang
Apabila pengecualian berlaku dalam benang, ia tidak dipanjangkan secara langsung ke benang utama, jadi satu kaedah diperlukan untuk menangkap dan berkongsi pengecualian itu.
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)- Dengan meletakkan pengecualian ke dalam
Queuedan mengambilnya dalam utas utama, anda boleh mengesan kegagalan dengan lebih pasti. Jika anda menggunakanconcurrent.futures.ThreadPoolExecutor, pengecualian akan dilemparkan semula denganfuture.result(), menjadikannya lebih mudah untuk dikendalikan.
GIL (Global Interpreter Lock) dan Kesan-Kesannya
Disebabkan mekanisme GIL (Global Interpreter Lock) dalam CPython, pelbagai bait kod Python tidak dijalankan serentak dalam proses yang sama. Bagi tugasan yang menggunakan CPU secara intensif, seperti pengiraan berat, adalah disarankan untuk menggunakan multiprocessing. Sebaliknya, untuk tugasan terikat I/O seperti pembacaan fail atau komunikasi rangkaian, threading berfungsi dengan berkesan.
Ringkasan
Dengan menggunakan modul threading Python, anda boleh melaksanakan program multibenang dan melaksanakan pelbagai proses secara serentak. Dengan mekanisme penyegerakan seperti Lock dan Condition, anda boleh mengakses sumber bersama dengan selamat dan melakukan penyegerakan yang kompleks. Sebagai tambahan, menggunakan benang daemon atau ThreadPoolExecutor, pengurusan benang dan pemprosesan selari yang cekap menjadi lebih mudah.
Anda boleh mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Sila lihat juga saluran YouTube kami.