Modul `threading` di Python

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 worker dijalankan di thread terpisah, sementara thread utama terus beroperasi. Dengan memanggil metode join(), 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 Lock digunakan 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 Condition agar 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 worker dibuat 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())
  • ThreadPoolExecutor membuat kumpulan thread dan memproses tugas secara efisien. Tentukan jumlah thread untuk dijalankan secara bersamaan dengan max_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 Event dan melanjutkan pemrosesan saat thread utama memanggil event.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 Queue dan mengambilnya di thread utama, Anda dapat mendeteksi kegagalan secara andal. Jika Anda menggunakan concurrent.futures.ThreadPoolExecutor, eksepsi akan dilempar ulang dengan future.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.

YouTube Video