Kursus
Jika Anda baru mulai belajar Python dan ingin mempelajari lebih lanjut, ikuti kursus Introduction to Data Science in Python dari DataCamp.

Pendahuluan
Threading memungkinkan bagian-bagian berbeda dari proses Anda berjalan secara bersamaan (Sumber: RealPython). Bagian-bagian ini biasanya berdiri sendiri dan memiliki unit eksekusi terpisah yang berada dalam proses yang sama. Proses tidak lain adalah program yang sedang berjalan yang memiliki unit-unit individual yang dapat dijalankan secara bersamaan. Misalnya, peramban web bisa menjadi sebuah proses, aplikasi yang menjalankan banyak kamera secara simultan bisa menjadi proses; gim video adalah contoh lain dari proses.
Di dalam proses terdapat konsep threading ganda atau yang biasa dikenal sebagai multi-threading, di mana banyak thread bekerja bersama untuk mencapai tujuan bersama. Manfaat paling krusial dari penggunaan thread adalah memungkinkan Anda menjalankan program secara paralel.
Mari pahami konsep threading dengan sebuah contoh. Bayangkan Anda memiliki aplikasi yang menghitung jumlah mobil yang masuk dan keluar parkir pusat perbelanjaan. Perangkat Anda memiliki berbagai kamera yang memantau pintu masuk dan keluar yang terhubung ke perangkat pusat. Setiap kamera akan memiliki algoritme untuk memantau arus mobil, yang akan berada dalam proses atau program yang sama. Namun, setiap kamera, bersama dengan algoritme yang dijalankannya, bisa menjadi bagian dari thread yang terpisah. Bukan hanya itu, bahkan frame yang dibaca dari kamera dan algoritme yang melakukan prediksi pada frame tersebut juga bisa menjadi dua thread yang terpisah.
Contoh lain adalah gim video di mana proses harus menjalankan tugas-tugas secara paralel seperti grafis, interaksi pengguna, dan jaringan (saat bermain gim multipemain) karena harus selalu responsif. Untuk mencapai hal ini, konsep multi-threading harus digunakan, di mana setiap thread bertanggung jawab menjalankan setiap tugas yang independen dan individual.
Sebuah thread memiliki alur eksekusinya sendiri, yang berarti proses akan memiliki banyak hal yang terjadi pada satu waktu.
Penting untuk dicatat bahwa setiap proses memiliki setidaknya satu thread, yang disebut main thread. Jika program Anda tidak memiliki thread yang didefinisikan, maka setidaknya akan ada main thread, yaitu proses itu sendiri.
Jika Anda melihat diagram di atas, ada sebuah proses yang terdiri dari main thread yang memuat variabel global dan tiga thread berbeda t1, t2, dan t3. Setiap thread memiliki variabel lokalnya sendiri dan alur eksekusi kodenya. Thread umumnya berbagi sumber daya proses, seperti sumber daya yang tersedia untuk main thread juga akan dibagi di antara tiga thread t1, t2, dan t3. Semua thread akan memiliki akses ke variabel global sambil tetap memiliki variabel lokal masing-masing.
Ada berbagai jenis thread:
- Kernel thread
- User thread
- Kombinasi kernel dan user thread
Sekarang mari kita lihat beberapa keuntungan menggunakan threading dalam program Anda:
-
Multi-threading memungkinkan program mempercepat eksekusi asalkan memiliki beberapa CPU.
-
Ini juga memungkinkan Anda melakukan tugas lain saat operasi I/O sedang dilakukan dengan bantuan beberapa thread atau bahkan main thread bersama dengan satu thread. Misalnya, kecepatan pembacaan frame dari kamera dan inferensi oleh algoritme akan ditangani oleh thread yang berbeda. Dengan demikian, algoritme tidak perlu menunggu frame diinput, dan bagian pembacaan frame tidak perlu menunggu eksekusi algoritme selesai untuk dapat membaca frame berikutnya.
- Thread dalam proses yang sama dapat berbagi memori dan sumber daya dari main thread.
Tantangan Threading
-
Ingat bahwa Python bekerja berdasarkan implementasi CPython, yang membatasi hanya satu thread yang dapat dijalankan pada satu waktu, sehingga threading mungkin tidak mempercepat semua tugas. Alasan penting di balik ini adalah Global Interpreter Lock (GIL).
Jika Anda ingin mempelajari tentang GIL, silakan lihat tutorial ini.
-
Jika Anda ingin mempercepat tugas yang intensif CPU, maka threading mungkin bukan solusi terbaik. Dalam kasus seperti itu, multi-processing dianggap bermanfaat.
-
Untuk mengeksekusi program yang terdiri dari berbagai thread, program perlu beralih dengan cepat antar-thread; oleh karena itu, penjadwalan perlu diperhatikan dalam program dengan banyak thread.
-
Berbagi sumber daya juga bisa menjadi masalah karena semua thread berbagi sumber daya dan memori dari variabel global. Dengan demikian, operasi yang dilakukan dalam satu thread dapat menyebabkan kesalahan memori bagi thread lain, atau thread lain mungkin tidak mendapatkan memori untuk melakukan tugasnya.
Threading di Python
-
Di Python, modul threading adalah modul bawaan yang dikenal sebagai
threadingdan dapat langsung diimpor. -
Karena hampir semua hal di Python direpresentasikan sebagai objek, threading juga merupakan objek di Python. Sebuah thread mampu
- Menyimpan data,
- Disimpan dalam struktur data seperti dictionary, list, set, dll.
- Dapat diteruskan sebagai parameter ke sebuah fungsi.
-
Sebuah thread juga dapat dijalankan sebagai proses.
-
Sebuah thread di Python dapat memiliki berbagai status seperti:
- Wait,
- Locked.
Sekarang mari kita pelajari cara mengimplementasikan threading di Python.
Modul Thread di Python3
Perhatikan bahwa Python3 kompatibel mundur dengan modul thread yang ada di Python2.7. Di Python3, modul ini dapat diimpor sebagai _thread. Jadi, mari ambil contoh dan pahami modul _thread.
import _thread #thread module imported
import time #time module
Mari definisikan fungsi bernama thread_delay, yang akan menerima dua parameter sebagai masukan, yaitu nama thread dan delay. Di dalam fungsi ini, Anda akan:
- Mendefinisikan counter dengan nilai nol,
- Lalu melakukan perulangan dengan while loop yang akan berjalan tiga kali,
- Di dalam while loop, Anda akan menambahkan
time.sleep()untuk menambah jeda; jeda ini akan berguna untuk memahami eksekusi thread. Jeda ini dalam hitungan detik, - Kemudian Anda akan menambah counter sebesar 1,
- Untuk melihat apakah eksekusi thread berjalan lancar, Anda akan mencetak nama thread dan waktu saat thread dieksekusi.
def thread_delay(thread_name, delay):
count = 0
while count < 3:
time.sleep(delay)
count += 1
print(thread_name, '-------->', time.time())
Sekarang untuk menambahkan fungsionalitas thread pada fungsi di atas atau menjalankan fungsi tersebut di dalam thread, Anda akan menggunakan metode start_new_thread yang ada di dalam _thread module.
Mari lihat docstring dari metode start_new_thread.
?_thread.start_new_thread

Mari masukkan fungsi thread_delay tanpa tanda kurung dan dua argumen, yaitu nama thread dan delay (untuk memvisualisasikan eksekusi thread karena sangat cepat).
_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 3))
10916
t1 --------> 1588077856.4221454
t1 --------> 1588077857.4418247
t2 --------> 1588077858.422453
t1 --------> 1588077858.4431
t2 --------> 1588077861.4371448
t2 --------> 1588077864.438915
Dari keluaran di atas, Anda dapat melihat bahwa thread t1 mulai dieksekusi terlebih dahulu. Sementara itu, thread t2 menunggu karena ada jeda 3 detik, dan begitu jeda itu selesai, thread t2 dieksekusi, dan juga, jeda untuk thread t1 hanya 1 detik.
Sekarang mari ubah jeda t2 menjadi 5 detik, yang akan memulai t2 setelah t1 selesai dieksekusi karena t1 hanya membutuhkan 3 detik untuk menyelesaikan eksekusinya.
_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 5))
14632
t1 --------> 1588078001.4087512
t1 --------> 1588078002.4272866
t1 --------> 1588078003.432218
t2 --------> 1588078005.4082909
t2 --------> 1588078010.4268317
t2 --------> 1588078015.4283602
Mengimplementasikan threading menggunakan modul Threading
Mari gunakan contoh yang sama seperti di atas, tetapi kali ini Anda akan menggunakan modul threading alih-alih modul _thread.
import threading
import time
def thread_delay(thread_name, delay):
count = 0
while count < 3:
time.sleep(delay)
count += 1
print(thread_name, '-------->', time.time())
Di dalam modul threading terdapat kelas Thread, yang semangatnya mirip dengan fungsi start_new_thread dari modul _thread.
Mari lihat docstring dari kelas Thread, yang menerima beberapa parameter seperti group, target (seperti fungsi), args, dll.
?threading.Thread

Di konstruktor kelas Thread, Anda akan memasukkan fungsi target thread_delay dan argumen dari fungsi tersebut.
t1 = threading.Thread(target=thread_delay, args=('t1', 1))
t2 = threading.Thread(target=thread_delay, args=('t2', 3))
Di modul threading, untuk menjalankan atau mengeksekusi thread, Anda menggunakan metode start(), yang bertugas menjalankan thread.
?t1.start

t1.start()
t2.start()
t1 --------> 1588081121.7790291
t1 --------> 1588081122.7962046
t2 --------> 1588081123.7789369
t1 --------> 1588081123.7980876
t2 --------> 1588081126.7961557
t2 --------> 1588081129.7995002
Anda juga akan menggunakan metode join, yang berarti menunggu hingga semua eksekusi thread selesai. Jadi apa pun kode yang Anda tulis setelah metode join hanya akan dieksekusi setelah thread-thread ini berakhir.
?t1.join

t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
t1 --------> 1588081798.7118263
t1 --------> 1588081799.7231226
t2 --------> 1588081800.7129223
t1 --------> 1588081800.730732
t2 --------> 1588081803.7298765
t2 --------> 1588081806.7309563
Thread execution is complete!
Sekarang mari hitung volume kubus dan persegi menggunakan konsep threading, yang cukup langsung.
def volume_cube(a):
print ("Volume of Cube:", a*a*a)
def volume_square(a):
print ("Volume of Square:", a*a)
t1 = threading.Thread(target=volume_cube, args=(2))
t2 = threading.Thread(target=volume_square, args=(3))
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Thread execution is complete!
Exception in thread Thread-9:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_square() argument after * must be an iterable, not int
Exception in thread Thread-8:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_cube() argument after * must be an iterable, not int
Tunggu; apa? Begitu Anda mengeksekusi metode start, muncul error. Itu karena parameter args mengharapkan sebuah tuple, dan karena volume_cube dan volume_square hanya mengharapkan satu parameter, maka Anda perlu menambahkan koma pada parameter args setelah menentukan argumen pertama dari fungsi tersebut.
t1 = threading.Thread(target=volume_cube, args=(2,))
t2 = threading.Thread(target=volume_square, args=(3,))
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Volume of Cube: 8
Volume of Square: 9
Thread execution is complete!
Sekarang mari pelajari menggunakan threading sebagai subkelas.
Threading sebagai subkelas
Di bagian ini, Anda akan mempelajari cara membuat subkelas dari kelas thread yang tersedia di dalam modul threading, lalu membuat instance thread t1 dan t2 dari kelas tersebut.
Anda juga akan menggunakan fungsi thread_delay yang sama di sini.
class DataCampThread(threading.Thread):
def __init__(self, name, delay):
threading.Thread.__init__(self)
self.name = name
self.delay = delay
def run(self):
print('Starting Thread:', self.name)
thread_delay(self.name,self.delay)
print('Execution of Thread:', self.name, 'is complete!')
t1 = DataCampThread('t1', 1)
t2 = DataCampThread('t2', 3)
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Starting Thread: t1
Starting Thread: t2
t1 --------> 1588089083.4879806
t1 --------> 1588089084.489222
t1 --------> 1588089085.4909666
Execution of Thread: t1 is complete!
t2 --------> 1588089085.4979634
t2 --------> 1588089088.499365
t2 --------> 1588089091.5012596
Execution of Thread: t2 is complete!
Thread execution is complete!
Sekarang mari pahami kode di atas selangkah demi selangkah:
-
Anda mendefinisikan kelas
DataCampThread, dan pada argumen kelas, Anda meneruskan kelasthreading.Thread. Alasannya adalah Anda ingin membuat subkelas dari modul threading. Dengan melakukan ini, Anda dapat menggunakan semua metode yang tersedia di kelasthreading.Threadserupa dengan konsep pewarisan. -
Selanjutnya, Anda mendefinisikan metode init dari kelas DataCampThread dan meneruskan fungsi bersama dengan delay. Init adalah konstruktor yang mirip dengan parameter yang Anda teruskan ke kelas
threading.Thread; di sini Anda akan meneruskannya ke kelasDataCampThread. -
Kemudian Anda menginisialisasi metode init dari kelas utama threading.Thread dan lalu menyetarakan name dan delay.
-
Berikutnya, Anda mendefinisikan metode
run; ingat di sini Anda mendefinisikan metodestart, yang pada dasarnya di balik layar memanggil metoderun. Karena Anda telah mewarisi kelas utama, Anda diperbolehkan melakukan perubahan pada metoderun. Jadi, ketika Anda memanggil metodestart, apa pun modifikasi yang Anda lakukan pada metoderunakan tercermin sekarang.
Kesimpulan
Selamat telah menyelesaikan tutorial ini.
Tutorial ini merupakan pengantar dasar threading di Python. Namun, threading adalah topik yang sangat luas dan penting, dan beberapa konsep mungkin belum dibahas. Silakan telusuri lebih banyak metode threading seperti active count, current thread, dan sebagainya.
Jika Anda ingin belajar lebih jauh, lihat juga Thread-based Parallelism di Python.
Jika Anda baru mulai belajar Python dan ingin mempelajari lebih lanjut, ikuti kursus Introduction to Data Science in Python dari DataCamp.
