Python Threading: Sebuah Pengantar

Diterbitkan: 2022-10-25

Dalam tutorial ini, Anda akan belajar cara menggunakan modul threading bawaan Python untuk menjelajahi kemampuan multithreading di Python.

Dimulai dengan dasar-dasar proses dan utas, Anda akan mempelajari cara kerja multithreading dengan Python—sambil memahami konsep konkurensi dan paralelisme. Anda kemudian akan mempelajari cara memulai dan menjalankan satu atau lebih utas dengan Python menggunakan modul threading .

Mari kita mulai.

Proses vs. Utas: Apa Perbedaannya?

Apa Itu Proses?

Proses adalah setiap instance dari program yang perlu dijalankan.

Itu bisa apa saja – skrip Python atau browser web seperti Chrome hingga aplikasi konferensi video. Jika Anda meluncurkan Task Manager pada mesin Anda dan menavigasi ke Performance -> CPU , Anda akan dapat melihat proses dan thread yang sedang berjalan pada core CPU Anda.

cpu-proc-utas

Memahami Proses dan Utas

Secara internal, suatu proses memiliki memori khusus yang menyimpan kode dan data yang terkait dengan proses tersebut.

Sebuah proses terdiri dari satu atau lebih thread . Thread adalah urutan instruksi terkecil yang dapat dijalankan oleh sistem operasi, dan mewakili aliran eksekusi.

Setiap utas memiliki tumpukan dan registernya sendiri tetapi bukan memori khusus. Semua utas yang terkait dengan suatu proses dapat mengakses data. Oleh karena itu, data dan memori dibagi oleh semua utas proses.

proses-dan-utas

Dalam CPU dengan N core, N proses dapat dieksekusi secara paralel pada waktu yang sama. Namun, dua utas dari proses yang sama tidak pernah dapat dieksekusi secara paralel - tetapi dapat dieksekusi secara bersamaan. Kami akan membahas konsep konkurensi vs paralelisme di bagian berikutnya.

Berdasarkan apa yang telah kita pelajari sejauh ini, mari kita rangkum perbedaan antara proses dan utas.

Fitur Proses Benang
Penyimpanan Memori khusus Berbagi memori
Modus eksekusi Paralel, bersamaan Bersamaan; tapi tidak paralel
Eksekusi ditangani oleh Sistem operasi CPython Interpreter

Multithreading dengan Python

Dengan Python, Global Interpreter Lock (GIL) memastikan bahwa hanya satu utas yang dapat memperoleh kunci dan dijalankan kapan saja. Semua utas harus mendapatkan kunci ini untuk dijalankan. Ini memastikan bahwa hanya satu utas yang dapat dieksekusi—pada titik waktu tertentu—dan menghindari multithreading secara bersamaan .

Misalnya, pertimbangkan dua utas, t1 dan t2 , dari proses yang sama. Karena utas berbagi data yang sama ketika t1 membaca nilai tertentu k , t2 dapat memodifikasi nilai k yang sama . Hal ini dapat menyebabkan kebuntuan dan hasil yang tidak diinginkan. Tetapi hanya satu utas yang dapat memperoleh kunci dan dijalankan kapan saja. Oleh karena itu, GIL juga memastikan keamanan utas .

Jadi bagaimana kita mencapai kemampuan multithreading dengan Python? Untuk memahami ini, mari kita bahas konsep konkurensi dan paralelisme.

Konkurensi vs. Paralelisme: Sebuah Tinjauan

Pertimbangkan CPU dengan lebih dari satu inti. Pada ilustrasi di bawah, CPU memiliki empat inti. Ini berarti bahwa kita dapat menjalankan empat operasi berbeda secara paralel pada saat tertentu.

Jika terdapat empat proses, maka masing-masing proses dapat berjalan secara independen dan simultan pada masing-masing dari keempat core tersebut. Mari kita asumsikan bahwa setiap proses memiliki dua utas.

multicore-paralelisme

Untuk memahami cara kerja threading, mari kita beralih dari arsitektur prosesor multicore ke single-core. Seperti yang disebutkan, hanya satu utas yang dapat aktif pada contoh eksekusi tertentu; tetapi inti prosesor dapat beralih di antara utas.

kode

Misalnya, utas terikat I/O sering menunggu operasi I/O: membaca input pengguna, membaca database, dan operasi file. Selama waktu tunggu ini, ia dapat melepaskan kunci sehingga utas lainnya dapat berjalan. Waktu tunggu juga dapat berupa operasi sederhana seperti tidur selama n detik.

Singkatnya: Selama operasi menunggu, utas melepaskan kunci, memungkinkan inti prosesor untuk beralih ke utas lain. Utas sebelumnya melanjutkan eksekusi setelah masa tunggu selesai. Proses ini, di mana inti prosesor beralih di antara utas secara bersamaan, memfasilitasi multithreading.

Jika Anda ingin menerapkan paralelisme tingkat proses dalam aplikasi Anda, pertimbangkan untuk menggunakan multiprosesor.

Modul Threading Python: Langkah Pertama

Python dikirimkan dengan modul threading yang dapat Anda impor ke dalam skrip Python.

 import threading

Untuk membuat objek utas dengan Python, Anda dapat menggunakan konstruktor Thread : threading.Thread(...) . Ini adalah sintaks generik yang cukup untuk sebagian besar implementasi threading:

 threading.Thread(target=...,args=...)

Di Sini,

  • target adalah argumen kata kunci yang menunjukkan panggilan Python
  • args adalah tupel argumen yang diterima target.

Anda memerlukan Python 3.x untuk menjalankan contoh kode dalam tutorial ini. Unduh kodenya dan ikuti.

Cara Mendefinisikan dan Menjalankan Utas dengan Python

Mari kita definisikan utas yang menjalankan fungsi target.

Fungsi target adalah some_func .

 import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())

Mari kita urai apa yang dilakukan cuplikan kode di atas:

  • Ini mengimpor modul threading dan time .
  • Fungsi some_func memiliki pernyataan deskriptif print() dan menyertakan operasi tidur selama dua detik: time.sleep(n) menyebabkan fungsi tidur selama n detik.
  • Selanjutnya, kita mendefinisikan thread thread_1 dengan target sebagai some_func . threading.Thread(target=...) membuat objek thread.
  • Catatan : Tentukan nama fungsi dan bukan pemanggilan fungsi; gunakan some_func dan bukan some_func() .
  • Membuat objek utas tidak memulai utas; memanggil metode start() pada objek utas tidak.
  • Untuk mendapatkan jumlah utas aktif, kami menggunakan fungsi active_count() .

Skrip Python berjalan di utas utama, dan kami membuat utas lain ( thread1 ) untuk menjalankan fungsi some_func sehingga jumlah utas aktif adalah dua, seperti yang terlihat pada output:

 # Output Running some_func... 2 Finished running some_func.

Jika kita melihat lebih dekat pada output, kita melihat bahwa saat memulai thread1 , pernyataan print pertama berjalan. Namun selama operasi tidur, prosesor beralih ke utas utama dan mencetak jumlah utas yang aktif—tanpa menunggu thread1 selesai dieksekusi.

thread1-mantan

Menunggu Utas Selesai Eksekusi

Jika Anda ingin thread1 menyelesaikan eksekusi, Anda dapat memanggil metode join() di atasnya setelah memulai thread. Melakukannya akan menunggu thread1 menyelesaikan eksekusi tanpa beralih ke utas utama.

 import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())

Sekarang, thread1 telah selesai dieksekusi sebelum kita mencetak jumlah thread yang aktif. Jadi hanya utas utama yang berjalan, yang berarti jumlah utas aktif adalah satu.

 # Output Running some_func... Finished running some_func. 1

Cara Menjalankan Banyak Utas dengan Python

Selanjutnya, mari kita buat dua utas untuk menjalankan dua fungsi yang berbeda.

Di sini, count_down adalah fungsi yang mengambil angka sebagai argumen dan menghitung mundur dari angka itu ke nol.

 def count_down(n): for i in range(n,-1,-1): print(i)

Kami mendefinisikan count_up , fungsi Python lain yang menghitung dari nol hingga angka tertentu.

 def count_up(n): for i in range(n+1): print(i)

Saat menggunakan fungsi range() dengan sintaks range(start, stop, step) , titik akhir stop dikecualikan secara default.

– Untuk menghitung mundur dari angka tertentu ke nol, Anda dapat menggunakan nilai step negatif -1 dan mengatur nilai stop ke -1 sehingga nol disertakan.

– Demikian pula, untuk menghitung hingga n , Anda harus mengatur nilai stop ke n + 1 . Karena nilai default start dan step masing-masing adalah 0 dan 1, Anda dapat menggunakan range(n + 1) untuk mendapatkan urutan 0 hingga n.

Selanjutnya, kita mendefinisikan dua utas, thread1 dan thread2 untuk menjalankan fungsi count_down dan count_up , masing-masing. Kami menambahkan pernyataan print dan operasi sleep untuk kedua fungsi tersebut.

Saat membuat objek utas, perhatikan bahwa argumen ke fungsi target harus ditetapkan sebagai tupel—untuk parameter args . Karena kedua fungsi ( count_down dan count_up ) mengambil satu argumen. Anda harus memasukkan koma secara eksplisit setelah nilainya. Ini memastikan argumen masih diteruskan sebagai Tuple, karena elemen berikutnya disimpulkan sebagai None .

 import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") print(i) time.sleep(1) thread1 = threading.Thread(target=count_down,args=(10,)) thread2 = threading.Thread(target=count_up,args=(5,)) thread1.start() thread2.start()

Dalam keluaran:

  • Fungsi count_up berjalan pada thread2 dan menghitung hingga 5 mulai dari 0.
  • Fungsi count_down berjalan pada thread1 menghitung mundur dari 10 ke 0.
 # Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0

Anda dapat melihat bahwa thread1 dan thread2 dijalankan secara alternatif, karena keduanya melibatkan operasi menunggu (tidur). Setelah fungsi count_up selesai menghitung hingga 5, thread2 tidak lagi aktif. Jadi kami mendapatkan output yang sesuai dengan hanya thread1 .

Menyimpulkan

Dalam tutorial ini, Anda telah belajar cara menggunakan modul threading bawaan Python untuk mengimplementasikan multithreading. Berikut ringkasan dari takeaways utama:

  • Konstruktor Thread dapat digunakan untuk membuat objek thread. Menggunakan threading.Thread(target=<callable>,args=(<tuple of args>)) membuat utas yang menjalankan target yang dapat dipanggil dengan argumen yang ditentukan dalam args .
  • Program Python berjalan pada utas utama, sehingga objek utas yang Anda buat adalah utas tambahan. Anda dapat memanggil fungsi active_count() mengembalikan jumlah utas aktif kapan saja.
  • Anda dapat memulai thread menggunakan metode start() pada objek thread dan menunggu hingga eksekusi selesai menggunakan metode join() .

Anda dapat membuat kode contoh tambahan dengan mengubah waktu tunggu, mencoba operasi I/O yang berbeda, dan banyak lagi. Pastikan untuk mengimplementasikan multithreading dalam proyek Python Anda yang akan datang. Selamat mengkode!