Menguasai Multithreading Python: GIL Dijelaskan dengan Contoh Praktis
Python menawarkan baik multiprocessing maupun multithreading untuk mempercepat beban kerja. Panduan ini memandu Anda melalui pembuatan aplikasi berbasis thread yang kuat, nuansa Global Interpreter Lock (GIL), dan cara menghindari jebakan umum seperti deadlock dan race condition.
Apa itu Thread?
Thread adalah unit eksekusi ringan dalam satu proses. Thread berbagi ruang memori proses, memungkinkan pertukaran data yang efisien, sementara sistem operasi menjadwalkan mereka untuk berjalan secara bersamaan pada satu atau lebih inti CPU.
Apa itu Proses?
Proses adalah instance program yang independen. Saat Anda meluncurkan aplikasi—katakanlah peramban atau editor teks—OS membuat proses terpisah dengan kode, data, dan peta memori sendiri.
Dasar-Dasar Multithreading Python
Modul threading Python menyediakan API tingkat tinggi untuk mengelola thread, sedangkan modul legacy _thread sudah usang dan hanya dipertahankan untuk kompatibilitas mundur. Thread ideal untuk tugas I/O-bound, sementara tugas CPU-bound harus menggunakan modul multiprocessing untuk melewati GIL.
Thread: memori bersama, ringan.
Proses: memori terisolasi, lebih berat.
GIL: mutex yang melindungi eksekusi bytecode Python.
Kapan Menggunakan Multithreading
Multithreading unggul saat Anda perlu melakukan beberapa operasi I/O-beban—permintaan jaringan, I/O berkas, atau panggilan basis data—secara bersamaan. Thread yang dirancang dengan baik dapat meningkatkan responsivitas dan throughput tanpa overhead pembuatan proses baru.
Modul Python Utama untuk Threading
_thread – Tingkat rendah, usang.
threading – Modern, kaya fitur, dan pilihan yang direkomendasikan.
Menggunakan Modul threading
Modul threading menawarkan kelas Thread, primitif sinkronisasi (Lock, RLock, Semaphore, Condition, Event, Barrier), dan fungsi utilitas seperti activeCount() dan enumerate(). Di bawah ini adalah contoh minimal yang mendemonstrasikan pembuatan dan sinkronisasi thread.
import time import _thread def thread_test(name, wait): for i in range(4): time.sleep(wait) print(f"Running {name}") print(f"{name} has finished execution") if __name__ == "__main__": _thread.start_new_thread(thread_test, ("First Thread", 1)) _thread.start_new_thread(thread_test, ("Second Thread", 2)) _thread.start_new_thread(thread_test, ("Third Thread", 3))
Menjalankan kode ini menghasilkan output yang berselisih yang mendemonstrasikan eksekusi bersamaan.
Penjelasan
Impor time dan modul legacy _thread.
Definisikan thread_test untuk melakukan tugas dan mencatat progresnya.
Luncurkan tiga thread independen melalui start_new_thread.
Threading Modern dengan threading.Thread
Dalam praktiknya, Anda akan subclass threading.Thread dan menimpa metode run() miliknya. Contoh berikut mencermiliki perilaku sebelumnya tetapi menggunakan API tingkat tinggi.
import time import threading class ThreadTester(threading.Thread): def __init__(self, name, delay): super().__init__() self.name = name self.delay = delay def run(self): for _ in range(4): time.sleep(self.delay) print(f"Running {self.name}") print(f"{self.name} has finished execution") if __name__ == "__main__": threads = [ ThreadTester("First Thread", 1), ThreadTester("Second Thread", 2), ThreadTester("Third Thread", 3), ] for t in threads: t.start() for t in threads: t.join()
Output:
Penjelasan
Subclass Thread untuk mengenkapsulasi perilaku thread.
Timpa __init__ untuk menyimpan data spesifik thread.
Timpa run untuk menentukan tugas.
Buat instance, mulai, dan gabungkan setiap thread.
Jebakan Umum Threading
Deadlock
Deadlock terjadi ketika dua atau lebih thread menunggu tanpa batas untuk sumber daya yang dipegang oleh satu sama lain. Ilustrasi klasik: masalah Parafilosof Makan—lima filosofa berlomba untuk lima garpu, masing-masing membutuhkan dua garpu untuk makan. Jika setiap filosofa mengambil garpu kanan terlebih dahulu, siklus tunggu circular timbul, menghentikan semua progres.
Race Condition
Ketika beberapa thread memodifikasi data bersama tanpa koordinasi yang tepat, keadaan akhir dapat tidak dapat diprediksi. Misalnya:
i = 0 for _ in range(100): print(i) i += 1
Meluncurkan loop ini dalam thread paralel dapat menghasilkan urutan yang acak karena peningkatan tumpang tindih.
Menyinkronkan Thread
Modul threading menyediakan beberapa primitif sinkronisasi. Yang paling dasar adalah Lock, yang memastikan akses eksklusif ke sumber daya bersama.
Output menunjukkan akses yang diserialkan ke bagian kritis.
Alat Sinkronisasi Lainnya
RLock – kunci yang dapat dimasukkan ulang.
Semaphore – mengontrol akses ke sejumlah sumber daya terbatas.
Condition – memungkinkan thread menunggu kondisi tertentu.
Event – bendera sederhana untuk sinyal antar-thread.
Barrier – memblokir serangkaian thread hingga semua mencapai titik.
Memahami Global Interpreter Lock (GIL)
CPython menggunakan GIL untuk menyederhanakan eksekusi bytecode di seluruh thread. Sementara ini melindungi struktur internal interpreter (misalnya, perhitungan referensi) dari race condition, ini juga membatasi paralelisme untuk tugas CPU-bound pada sistem multi-core.
Saat thread dimulai, ia memperoleh GIL.
Jika thread lain membutuhkan GIL, ia menunggu hingga thread pertama melepaskannya.
Thread I/O-bound melepaskan GIL selama pemblokiran panggilan, memungkinkan thread lain untuk berjalan.
Thread CPU-bound memegang GIL, mencegah thread lain mengeksekusi kode Python.
Akibatnya, multithreading di Python menghasilkan sedikit manfaat untuk beban kerja komputasi intensif; multiprocessing adalah pendekatan yang disukai dalam kasus tersebut.
Mengapa GIL Ada
Pengumpul sampah CPython bergantung pada perhitungan referensi. Tanpa kunci global, pembaruan serentak pada hitungan referensi akan menyebabkan race condition dan kerusakan memori. Kunci per-objek akan memperkenalkan overhead yang signifikan, jadi GIL diadopsi sebagai solusi sederhana dan efektif.
Kesimpulan Praktis
Gunakan threading untuk konkuren I/O-bound.
Pilih multiprocessing untuk paralelisme CPU-bound.
Selalu sinkronkan data bersama dengan kunci atau primitif tingkat tinggi.
Waspadai dampak GIL saat menskala di seluruh inti.
Subclass Thread dan timpa run() untuk kode yang bersih dan mudah dipelihara.