Multithreading dengan Python dengan Contoh Global Interpreter Lock (GIL)

Daftar Isi:

Anonim

Bahasa pemrograman python memungkinkan Anda untuk menggunakan multiprocessing atau multithreading. Dalam tutorial ini, Anda akan belajar bagaimana menulis aplikasi multithread dengan Python.

Apa itu Thread?

Utas adalah unit pemeriksaan pada pemrograman konkuren. Multithreading adalah teknik yang memungkinkan CPU menjalankan banyak tugas dari satu proses pada waktu yang sama. Utas ini dapat dijalankan satu per satu sambil membagikan sumber daya prosesnya.

Apa itu Proses?

Suatu proses pada dasarnya adalah program yang sedang dieksekusi. Saat Anda memulai aplikasi di komputer Anda (seperti browser atau editor teks), sistem operasi membuat suatu proses.

Apa itu Multithreading dengan Python?

Multithreading dalam pemrograman Python adalah teknik terkenal di mana banyak utas dalam suatu proses berbagi ruang datanya dengan utas utama yang membuat berbagi informasi dan komunikasi dalam utas menjadi mudah dan efisien. Benang lebih ringan dari proses. Multi utas dapat dieksekusi secara individual saat berbagi sumber daya proses mereka. Tujuan multithreading adalah menjalankan banyak tugas dan fungsi sel pada waktu yang bersamaan.

Apa itu Multiprocessing?

Multiprocessing memungkinkan Anda menjalankan beberapa proses yang tidak terkait secara bersamaan. Proses ini tidak membagikan sumber daya mereka dan berkomunikasi melalui IPC.

Python Multithreading vs Multiprocessing

Untuk memahami proses dan utas, pertimbangkan skenario ini: File .exe di komputer Anda adalah sebuah program. Saat Anda membukanya, OS memuatnya ke dalam memori, dan CPU mengeksekusinya. Contoh program yang sekarang sedang berjalan disebut proses.

Setiap proses akan memiliki 2 komponen dasar:

  • Kode
  • Data

Sekarang, suatu proses dapat berisi satu atau lebih sub-bagian yang disebut utas. Ini tergantung pada arsitektur OS ,. Anda dapat menganggap utas sebagai bagian dari proses yang dapat dijalankan secara terpisah oleh sistem operasi.

Dengan kata lain, ini adalah aliran instruksi yang dapat dijalankan secara independen oleh OS. Untaian dalam satu proses berbagi data dari proses itu dan dirancang untuk bekerja sama untuk memfasilitasi paralelisme.

Dalam tutorial ini, Anda akan belajar,

  • Apa itu Thread?
  • Apa itu Proses?
  • Apa itu Multithreading?
  • Apa itu Multiprocessing?
  • Python Multithreading vs Multiprocessing
  • Mengapa menggunakan Multithreading?
  • Python MultiThreading
  • Modul Thread dan Threading
  • Modul Benang
  • Modul Penguliran
  • Kebuntuan dan kondisi balapan
  • Sinkronisasi utas
  • Apa itu GIL?
  • Mengapa GIL dibutuhkan?

Mengapa menggunakan Multithreading?

Multithreading memungkinkan Anda memecah aplikasi menjadi beberapa sub-tugas dan menjalankan tugas-tugas ini secara bersamaan. Jika Anda menggunakan multithreading dengan benar, kecepatan, kinerja, dan rendering aplikasi Anda semuanya dapat ditingkatkan.

Python MultiThreading

Python mendukung konstruksi untuk multiprosesing dan juga multithreading. Dalam tutorial ini, Anda terutama akan berfokus pada penerapan aplikasi multithread dengan python. Ada dua modul utama yang dapat digunakan untuk menangani utas dengan Python:

  1. The benang modul, dan
  2. The threading modul

Namun, di python, ada juga yang disebut kunci interpreter global (GIL). Itu tidak memungkinkan untuk mendapatkan banyak kinerja dan bahkan dapat mengurangi kinerja beberapa aplikasi multithread. Anda akan mempelajari semua tentang itu di bagian selanjutnya dari tutorial ini.

Modul Thread dan Threading

Dua modul yang akan Anda pelajari dalam tutorial ini adalah modul thread dan modul threading .

Namun, modul utas sudah lama tidak digunakan lagi. Dimulai dengan Python 3, telah ditetapkan sebagai usang dan hanya dapat diakses sebagai __thread untuk kompatibilitas ke belakang.

Anda harus menggunakan modul penguliran tingkat yang lebih tinggi untuk aplikasi yang ingin Anda terapkan. Modul utas hanya dibahas di sini untuk tujuan pendidikan.

Modul Benang

Sintaks untuk membuat utas baru menggunakan modul ini adalah sebagai berikut:

thread.start_new_thread(function_name, arguments)

Baiklah, sekarang Anda telah membahas teori dasar untuk memulai coding. Jadi, buka IDLE atau notepad Anda dan ketik yang berikut ini:

import timeimport _threaddef thread_test(name, wait):i = 0while i <= 3:time.sleep(wait)print("Running %s\n" %name)i = i + 1print("%s has finished execution" %name)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))

Simpan file dan tekan F5 untuk menjalankan program. Jika semuanya dilakukan dengan benar, inilah output yang akan Anda lihat:

Anda akan mempelajari lebih lanjut tentang kondisi balapan dan cara menanganinya di bagian selanjutnya

PENJELASAN KODE

  1. Pernyataan ini mengimpor modul waktu dan utas yang digunakan untuk menangani eksekusi dan penundaan utas Python.
  2. Di sini, Anda telah mendefinisikan fungsi yang disebut thread_test, yang akan dipanggil dengan metode start_new_thread . Fungsi ini menjalankan loop sementara untuk empat iterasi dan mencetak nama utas yang memanggilnya. Setelah iterasi selesai, ia mencetak pesan yang mengatakan bahwa utas telah selesai dieksekusi.
  3. Ini adalah bagian utama dari program Anda. Di sini, Anda cukup memanggil metode start_new_thread dengan fungsi thread_test sebagai argumen.

    Ini akan membuat utas baru untuk fungsi yang Anda berikan sebagai argumen dan mulai mengeksekusinya. Perhatikan bahwa Anda dapat mengganti ini (thread _ test) dengan fungsi lain yang ingin Anda jalankan sebagai thread.

Modul Penguliran

Modul ini adalah implementasi tingkat tinggi dari penguliran dengan python dan standar de facto untuk mengelola aplikasi multithread. Ini menyediakan berbagai fitur jika dibandingkan dengan modul utas.

Struktur modul Threading

Berikut adalah daftar beberapa fungsi berguna yang ditentukan dalam modul ini:

Nama Fungsi Deskripsi
activeCount () Mengembalikan hitungan objek Thread yang masih hidup
currentThread () Mengembalikan objek saat ini dari kelas Thread.
menghitung() Mencantumkan semua objek Thread aktif.
isDaemon () Mengembalikan nilai true jika utas adalah daemon.
hidup() Mengembalikan nilai benar jika utas masih hidup.
Metode Kelas Benang
Mulailah() Memulai aktivitas utas. Itu harus dipanggil hanya sekali untuk setiap utas karena itu akan memunculkan kesalahan runtime jika dipanggil beberapa kali.
Lari() Metode ini menunjukkan aktivitas thread dan dapat diganti oleh kelas yang memperluas kelas Thread.
Ikuti() Ini memblokir eksekusi kode lain sampai utas tempat metode join () dipanggil diakhiri.

Backstory: Kelas Thread

Sebelum Anda mulai membuat kode program multithread menggunakan modul threading, sangat penting untuk memahami tentang kelas Thread. Kelas thread adalah kelas utama yang menentukan template dan operasi thread dengan python.

Cara paling umum untuk membuat aplikasi python multithread adalah dengan mendeklarasikan kelas yang memperluas kelas Thread dan menimpa metode run ().

Kelas Thread, secara ringkas, menandakan urutan kode yang berjalan di thread kontrol yang terpisah .

Jadi, saat menulis aplikasi multithread, Anda akan melakukan hal berikut:

  1. mendefinisikan kelas yang memperluas kelas Thread
  2. Ganti konstruktor __init__
  3. Menimpa run () metode

Setelah objek utas dibuat, metode start () bisa digunakan untuk memulai eksekusi aktivitas ini dan metode join () bisa digunakan untuk memblokir semua kode lain hingga aktivitas saat ini selesai.

Sekarang, mari coba gunakan modul threading untuk mengimplementasikan contoh Anda sebelumnya. Sekali lagi, jalankan IDLE Anda dan ketik yang berikut ini:

import timeimport threadingclass threadtester (threading.Thread):def __init__(self, id, name, i):threading.Thread.__init__(self)self.id = idself.name = nameself.i = idef run(self):thread_test(self.name, self.i, 5)print ("%s has finished execution " %self.name)def thread_test(name, wait, i):while i:time.sleep(wait)print ("Running %s \n" %name)i = i - 1if __name__=="__main__":thread1 = threadtester(1, "First Thread", 1)thread2 = threadtester(2, "Second Thread", 2)thread3 = threadtester(3, "Third Thread", 3)thread1.start()thread2.start()thread3.start()thread1.join()thread2.join()thread3.join()

Ini akan menjadi output ketika Anda menjalankan kode di atas:

PENJELASAN KODE

  1. Bagian ini sama dengan contoh kita sebelumnya. Di sini, Anda mengimpor modul waktu dan utas yang digunakan untuk menangani eksekusi dan penundaan utas Python.
  2. Dalam bit ini, Anda membuat kelas yang disebut threadtester, yang mewarisi atau memperluas kelas Thread dari modul threading. Ini adalah salah satu cara paling umum untuk membuat utas dengan python. Namun, Anda sebaiknya hanya mengganti konstruktor dan metode run () di aplikasi Anda. Seperti yang Anda lihat pada contoh kode di atas, metode __init__ (konstruktor) telah diganti.

    Demikian pula, Anda juga telah mengganti metode run () . Ini berisi kode yang ingin Anda jalankan di dalam utas. Dalam contoh ini, Anda telah memanggil fungsi thread_test ().

  3. Ini adalah metode thread_test () yang mengambil nilai i sebagai argumen, menguranginya sebesar 1 pada setiap iterasi dan mengulang seluruh kode hingga i menjadi 0. Dalam setiap iterasi, ia mencetak nama thread yang saat ini dijalankan dan tidur selama menunggu detik (yang juga dianggap sebagai argumen).
  4. thread1 = threadtester (1, "Thread Pertama", 1)

    Di sini, kami membuat utas dan meneruskan tiga parameter yang kami nyatakan di __init__. Parameter pertama adalah id dari thread, parameter kedua adalah nama thread, dan parameter ketiga adalah counter, yang menentukan berapa kali while loop harus dijalankan.

  5. thread2.start ()

    Metode start digunakan untuk memulai eksekusi utas. Secara internal, fungsi start () memanggil metode run () kelas Anda.

  6. thread3.join ()

    Metode join () memblokir eksekusi kode lain dan menunggu hingga thread yang dipanggilnya selesai.

Seperti yang telah Anda ketahui, utas yang berada dalam proses yang sama memiliki akses ke memori dan data dari proses itu. Akibatnya, jika lebih dari satu utas mencoba mengubah atau mengakses data secara bersamaan, kesalahan dapat terjadi.

Di bagian selanjutnya, Anda akan melihat berbagai jenis komplikasi yang dapat muncul ketika utas mengakses data dan bagian kritis tanpa memeriksa transaksi akses yang ada.

Kebuntuan dan kondisi balapan

Sebelum mempelajari tentang kebuntuan dan kondisi balapan, akan sangat membantu jika Anda memahami beberapa definisi dasar yang terkait dengan pemrograman bersamaan:

  • Bagian penting

    Ini adalah fragmen kode yang mengakses atau mengubah variabel bersama dan harus dilakukan sebagai transaksi atom.

  • Pengalih Konteks

    Ini adalah proses yang diikuti CPU untuk menyimpan status utas sebelum mengubah dari satu tugas ke tugas lainnya sehingga dapat dilanjutkan dari titik yang sama nanti.

Kebuntuan

Kebuntuan adalah masalah paling ditakuti yang dihadapi pengembang saat menulis aplikasi bersamaan / multithread dengan python. Cara terbaik untuk memahami kebuntuan adalah dengan menggunakan contoh soal ilmu komputer klasik yang dikenal sebagai Masalah Makan Filsuf.

Pernyataan masalah untuk filsuf makan adalah sebagai berikut:

Lima filsuf duduk di atas meja bundar dengan lima piring spageti (sejenis pasta) dan lima garpu, seperti yang ditunjukkan pada diagram.

Masalah Makan Filsuf

Pada waktu tertentu, seorang filsuf pasti sedang makan atau berpikir.

Selain itu, seorang filsuf harus mengambil dua garpu yang berdekatan dengannya (yaitu, garpu kiri dan kanan) sebelum dia dapat memakan spageti. Masalah kebuntuan terjadi ketika kelima filsuf mengambil garpu kanan mereka secara bersamaan.

Karena masing-masing filsuf memiliki satu garpu, mereka semua akan menunggu yang lain meletakkan garpu mereka. Akibatnya, tidak ada yang bisa makan spageti.

Demikian pula, dalam sistem bersamaan, kebuntuan terjadi ketika utas atau proses yang berbeda (filsuf) mencoba memperoleh sumber daya sistem bersama (garpu) pada saat yang sama. Akibatnya, tidak ada proses yang mendapat kesempatan untuk dieksekusi karena menunggu sumber daya lain yang dipegang oleh proses lain.

Kondisi Balapan

Kondisi balapan adalah kondisi program yang tidak diinginkan yang terjadi saat sistem menjalankan dua atau lebih operasi secara bersamaan. Misalnya, pertimbangkan loop for yang sederhana ini:

i=0; # a global variablefor x in range(100):print(i)i+=1;

Jika Anda membuat n jumlah utas yang menjalankan kode ini sekaligus, Anda tidak dapat menentukan nilai i (yang dibagikan oleh utas) ketika program menyelesaikan eksekusi. Ini karena dalam lingkungan multithreading nyata, utas dapat tumpang tindih, dan nilai i yang diambil dan dimodifikasi oleh utas dapat berubah di antara saat beberapa utas lain mengaksesnya.

Ini adalah dua kelas masalah utama yang dapat terjadi dalam aplikasi python multithread atau terdistribusi. Di bagian selanjutnya, Anda akan mempelajari cara mengatasi masalah ini dengan menyinkronkan utas.

Sinkronisasi utas

Untuk menangani kondisi balapan, kebuntuan, dan masalah berbasis thread lainnya, modul threading menyediakan objek Lock . Idenya adalah ketika utas menginginkan akses ke sumber daya tertentu, utas memperoleh kunci untuk sumber daya itu. Setelah utas mengunci sumber daya tertentu, tidak ada utas lain yang dapat mengaksesnya hingga kuncinya dilepaskan. Akibatnya, perubahan pada sumber daya akan bersifat atomik, dan kondisi balapan akan dihindari.

Lock adalah primitif sinkronisasi tingkat rendah yang diimplementasikan oleh modul __thread . Pada waktu tertentu, kunci dapat berada di salah satu dari 2 status: terkunci atau tidak terkunci. Ini mendukung dua metode:

  1. memperoleh()

    Saat status kunci tidak terkunci, memanggil metode perolehan () akan mengubah status menjadi terkunci dan kembali. Namun, jika status terkunci, panggilan untuk memperoleh () diblokir sampai metode release () dipanggil oleh thread lain.

  2. melepaskan()

    Metode release () digunakan untuk menyetel status tidak terkunci, yaitu melepaskan kunci. Itu dapat dipanggil oleh utas apa pun, tidak harus yang mendapatkan kunci.

Berikut adalah contoh penggunaan kunci di aplikasi Anda. Jalankan IDLE Anda dan ketik yang berikut ini:

import threadinglock = threading.Lock()def first_function():for i in range(5):lock.acquire()print ('lock acquired')print ('Executing the first funcion')lock.release()def second_function():for i in range(5):lock.acquire()print ('lock acquired')print ('Executing the second funcion')lock.release()if __name__=="__main__":thread_one = threading.Thread(target=first_function)thread_two = threading.Thread(target=second_function)thread_one.start()thread_two.start()thread_one.join()thread_two.join()

Sekarang, tekan F5. Anda akan melihat keluaran seperti ini:

PENJELASAN KODE

  1. Di sini, Anda hanya membuat kunci baru dengan memanggil fungsi pabrik threading.Lock () . Secara internal, Lock () mengembalikan instance kelas Lock beton paling efektif yang dikelola oleh platform.
  2. Dalam pernyataan pertama, Anda memperoleh kunci dengan memanggil metode acquiring (). Ketika kunci telah diberikan, Anda mencetak "kunci diperoleh" ke konsol. Setelah semua kode yang Anda inginkan agar dijalankan thread telah selesai dieksekusi, Anda melepaskan kunci dengan memanggil metode release ().

Teorinya bagus, tetapi bagaimana Anda tahu bahwa kunci itu benar-benar berfungsi? Jika Anda melihat hasilnya, Anda akan melihat bahwa setiap print statement dicetak tepat satu baris pada satu waktu. Ingatlah bahwa, dalam contoh sebelumnya, keluaran dari cetakan di mana serampangan karena beberapa utas mengakses metode print () pada saat yang bersamaan. Di sini, fungsi cetak dipanggil hanya setelah kunci diperoleh. Jadi, keluarannya ditampilkan satu per satu dan baris demi baris.

Selain kunci, python juga mendukung beberapa mekanisme lain untuk menangani sinkronisasi utas seperti yang tercantum di bawah ini:

  1. RLocks
  2. Semaphores
  3. Kondisi
  4. Acara, dan
  5. Hambatan

Kunci Penerjemah Global (dan cara menghadapinya)

Sebelum masuk ke detail GIL python, mari kita tentukan beberapa istilah yang akan berguna dalam memahami bagian yang akan datang:

  1. Kode terikat CPU: ini mengacu pada setiap bagian kode yang akan langsung dieksekusi oleh CPU.
  2. Kode terikat I / O: ini dapat berupa kode apa pun yang mengakses sistem file melalui 'OS
  3. CPython: ini adalah implementasi referensi dari Python dan dapat dideskripsikan sebagai penerjemah yang ditulis dalam C dan Python (bahasa pemrograman).

Apa itu GIL dengan Python?

Global Interpreter Lock (GIL) dalam python adalah kunci proses atau mutex yang digunakan saat menangani proses. Ini memastikan bahwa satu utas dapat mengakses sumber daya tertentu pada satu waktu dan juga mencegah penggunaan objek dan bytecode sekaligus. Ini menguntungkan program single-threaded dalam peningkatan kinerja. GIL dengan python sangat sederhana dan mudah diimplementasikan.

Kunci dapat digunakan untuk memastikan bahwa hanya satu utas yang memiliki akses ke sumber daya tertentu pada waktu tertentu.

Salah satu fitur Python adalah ia menggunakan kunci global pada setiap proses interpreter, yang berarti bahwa setiap proses memperlakukan interpreter python itu sendiri sebagai sumber daya.

Misalnya, Anda telah menulis program python yang menggunakan dua utas untuk melakukan operasi CPU dan 'I / O'. Ketika Anda menjalankan program ini, inilah yang terjadi:

  1. Penerjemah python membuat proses baru dan memunculkan utas
  2. Saat thread-1 mulai berjalan, pertama-tama thread tersebut akan memperoleh GIL dan menguncinya.
  3. Jika thread-2 ingin mengeksekusi sekarang, ia harus menunggu GIL dilepaskan meskipun prosesor lain gratis.
  4. Sekarang, misalkan thread-1 sedang menunggu operasi I / O. Pada saat ini, ia akan merilis GIL, dan thread-2 akan mendapatkannya.
  5. Setelah menyelesaikan operasi I / O, jika thread-1 ingin dijalankan sekarang, ia harus menunggu GIL dirilis oleh thread-2 lagi.

Karena ini, hanya satu utas yang dapat mengakses penerjemah kapan saja, artinya hanya akan ada satu utas yang mengeksekusi kode python pada titik waktu tertentu.

Ini baik-baik saja dalam prosesor inti tunggal karena akan menggunakan pemotongan waktu (lihat bagian pertama dari tutorial ini) untuk menangani utas. Namun, dalam kasus prosesor multi-inti, fungsi terikat CPU yang dijalankan pada beberapa utas akan berdampak besar pada efisiensi program karena tidak akan benar-benar menggunakan semua inti yang tersedia pada saat yang bersamaan.

Mengapa GIL dibutuhkan?

Pengumpul sampah CPython menggunakan teknik manajemen memori yang efisien yang dikenal sebagai penghitungan referensi. Begini cara kerjanya: Setiap objek di python memiliki jumlah referensi, yang meningkat ketika ditugaskan ke nama variabel baru atau ditambahkan ke wadah (seperti tupel, daftar, dll.). Demikian juga, jumlah referensi berkurang ketika referensi keluar dari ruang lingkup atau ketika pernyataan del dipanggil. Saat jumlah referensi suatu objek mencapai 0, itu sampah dikumpulkan, dan memori yang dialokasikan dibebaskan.

Tetapi masalahnya adalah variabel jumlah referensi rentan terhadap kondisi balapan seperti variabel global lainnya. Untuk mengatasi masalah ini, pengembang python memutuskan untuk menggunakan kunci juru bahasa global. Opsi lainnya adalah menambahkan kunci ke setiap objek yang akan mengakibatkan kebuntuan dan peningkatan overhead dari panggilan gain () dan release ().

Oleh karena itu, GIL adalah batasan yang signifikan untuk program python multithread yang menjalankan operasi berat terikat CPU (secara efektif menjadikannya single-threaded). Jika Anda ingin menggunakan beberapa inti CPU dalam aplikasi Anda, gunakan modul multiprosesing sebagai gantinya.

Ringkasan

  • Python mendukung 2 modul untuk multithreading:
    1. Modul __thread : Ini menyediakan implementasi tingkat rendah untuk threading dan sudah usang.
    2. modul threading : Ini menyediakan implementasi tingkat tinggi untuk multithreading dan merupakan standar saat ini.
  • Untuk membuat utas menggunakan modul threading, Anda harus melakukan hal berikut:
    1. Buat kelas yang memperluas kelas Thread .
    2. Ganti konstruktornya (__init__).
    3. Ganti metode run () -nya .
    4. Buat objek dari kelas ini.
  • Sebuah utas bisa dijalankan dengan memanggil metode start () .
  • Metode join () dapat digunakan untuk memblokir utas lain hingga utas ini (yang digunakan untuk bergabung) menyelesaikan eksekusi.
  • Kondisi balapan terjadi ketika beberapa utas mengakses atau mengubah sumber daya bersama pada waktu yang sama.
  • Ini dapat dihindari dengan Sinkronisasi utas.
  • Python mendukung 6 cara untuk menyinkronkan utas:
    1. Kunci
    2. RLocks
    3. Semaphores
    4. Kondisi
    5. Acara, dan
    6. Hambatan
  • Kunci memungkinkan hanya utas tertentu yang telah memperoleh kunci untuk memasuki bagian kritis.
  • Kunci memiliki 2 metode utama:
    1. memperoleh () : Ini mengatur status kunci menjadi terkunci. Jika dipanggil pada objek terkunci, itu memblokir hingga sumber daya gratis.
    2. release () : Ini mengatur status kunci menjadi tidak terkunci dan kembali. Jika dipanggil pada objek yang tidak terkunci, hasilnya adalah false.
  • Kunci interpreter global adalah mekanisme yang hanya dapat mengeksekusi 1 proses interpreter CPython dalam satu waktu.
  • Itu digunakan untuk memfasilitasi fungsionalitas penghitungan referensi dari pengumpul sampah CPythons.
  • Untuk membuat aplikasi Python dengan operasi berat terikat CPU, Anda harus menggunakan modul multiprosesing.