Modul `threading` di Python
Artikel ini menjelaskan modul threading di Python.
YouTube Video
Modul threading di Python
Modul threading di Python adalah pustaka standar yang mendukung pemrograman multithreading. Menggunakan thread memungkinkan beberapa proses berjalan secara bersamaan, yang dapat meningkatkan kinerja program, terutama dalam kasus yang melibatkan operasi pemblokiran seperti menunggu I/O. Karena Global Interpreter Lock (GIL) Python, efektivitas multithreading terbatas untuk operasi yang terikat CPU, tetapi bekerja secara efisien untuk operasi yang terikat I/O.
Bagian berikut menjelaskan dasar-dasar penggunaan modul threading dan cara mengontrol thread.
Penggunaan Dasar Thread
Membuat dan Menjalankan Thread
Untuk membuat thread dan melakukan pemrosesan bersamaan, gunakan kelas threading.Thread. Tentukan fungsi target untuk membuat thread dan menjalankan 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
workerdijalankan di thread terpisah, sementara thread utama terus beroperasi. Dengan memanggil metodejoin(), thread utama menunggu hingga sub-thread selesai.
Memberi Nama Thread
Memberi nama thread yang bermakna membuat pencatatan log dan debug menjadi lebih mudah. Anda dapat menentukan nama menggunakan 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 daftar thread yang sedang berjalan, yang berguna untuk debugging dan memantau status.
Menginheritasi Kelas Thread
Jika Anda ingin menyesuaikan kelas yang menjalankan thread, Anda dapat mendefinisikan kelas baru dengan menginheritasi 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, metode
run()dioverride untuk menentukan perilaku thread, sehingga setiap thread dapat memiliki datanya sendiri. Ini berguna ketika thread melakukan pemrosesan yang kompleks atau ketika Anda ingin setiap thread memiliki data independen sendiri.
Sinkronisasi Antar Thread
Ketika beberapa thread mengakses sumber daya bersama secara bersamaan, data race dapat terjadi. Untuk mencegah hal ini, modul threading menyediakan beberapa mekanisme sinkronisasi.
Kunci (Lock)
Objek Lock digunakan untuk mengimplementasikan kontrol eksklusif atas sumber daya antar thread. Saat satu thread mengunci sumber daya, thread lain tidak dapat mengakses sumber daya tersebut.
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 daya bersama, tetapi
Lockdigunakan untuk mencegah beberapa thread mengubah data secara bersamaan.
Reentrant Lock (RLock)
Jika thread yang sama perlu memperoleh kunci beberapa kali, gunakan RLock (reentrant lock). Ini berguna untuk pemanggilan rekursif atau pemanggilan pustaka yang mungkin memperoleh kunci di berbagai pemanggilan.
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, thread yang sama dapat memperoleh kembali kunci yang sudah dipegangnya, yang membantu mencegah deadlock pada penguncian secara bertingkat.
Kondisi (Condition)
Condition digunakan agar thread menunggu hingga kondisi tertentu terpenuhi. Ketika sebuah thread memenuhi suatu kondisi, Anda dapat memanggil notify() untuk memberitahu thread lain, atau notify_all() untuk memberitahu semua thread yang sedang menunggu.
Berikut adalah contoh seorang produsen dan konsumen 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()- Kode ini menggunakan
Conditionagar produsen memberikan notifikasi saat data ditambahkan, dan konsumen menunggu notifikasi tersebut sebelum mengambil data, sehingga tercapai sinkronisasi.
Daemonisasi Thread
Thread daemon dihentikan secara paksa ketika thread utama berakhir. Sementara thread normal harus menunggu untuk berhenti, thread daemon berhenti secara otomatis.
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, thread
workerdibuat menjadi daemon, sehingga dihentikan secara paksa ketika thread utama berakhir.
Manajemen Thread dengan ThreadPoolExecutor
Selain modul threading, Anda dapat menggunakan ThreadPoolExecutor dari modul concurrent.futures untuk mengelola kumpulan thread dan menjalankan tugas secara paralel.
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())ThreadPoolExecutormembuat kumpulan thread dan memproses tugas secara efisien. Tentukan jumlah thread untuk dijalankan secara bersamaan denganmax_workers.
Komunikasi Event Antar-Thread
Dengan menggunakan threading.Event, Anda dapat mengatur flag antar-thread untuk memberi tahu thread lain tentang terjadinya sebuah 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- Kode ini menunjukkan mekanisme di mana thread pekerja menunggu sinyal
Eventdan melanjutkan pemrosesan saat thread utama memanggilevent.set().
Penanganan Eksepsi dan Penghentian Thread di Dalam Thread
Ketika terjadi eksepsi dalam thread, eksepsi tersebut tidak langsung diteruskan ke thread utama, sehingga diperlukan pola untuk menangkap dan membagikan eksepsi.
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 memasukkan pengecualian ke dalam
Queuedan mengambilnya di thread utama, Anda dapat mendeteksi kegagalan secara andal. Jika Anda menggunakanconcurrent.futures.ThreadPoolExecutor, eksepsi akan dilempar ulang denganfuture.result(), sehingga lebih mudah untuk ditangani.
GIL (Global Interpreter Lock) dan Efeknya
Karena mekanisme GIL (Global Interpreter Lock) di CPython, beberapa bytecode Python sebenarnya tidak berjalan secara bersamaan dalam satu proses. Untuk tugas yang berat pada CPU, seperti komputasi berat, disarankan menggunakan multiprocessing. Di sisi lain, untuk tugas yang terkait I/O seperti pembacaan file atau komunikasi jaringan, threading bekerja secara efektif.
Ringkasan
Dengan menggunakan modul threading Python, Anda dapat mengimplementasikan program multithreading dan menjalankan beberapa proses secara bersamaan. Dengan mekanisme sinkronisasi seperti Lock dan Condition, Anda dapat mengakses sumber daya bersama dengan aman dan melakukan sinkronisasi yang kompleks. Selain itu, dengan menggunakan thread daemon atau ThreadPoolExecutor, manajemen thread dan pemrosesan paralel yang efisien menjadi lebih mudah.
Anda dapat mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Silakan periksa juga saluran YouTube kami.