Multithreading di Python dengan Contoh Global Interpreter Lock (GIL)

Isi kandungan:

Anonim

Bahasa pengaturcaraan python membolehkan anda menggunakan multiprosesan atau multithreading. Dalam tutorial ini, anda akan belajar bagaimana menulis aplikasi multithreaded di Python.

Apa itu Benang?

Suatu utas adalah unit pemeriksa pada pengaturcaraan serentak. Multithreading adalah teknik yang membolehkan CPU melaksanakan banyak tugas dari satu proses pada masa yang sama. Benang ini dapat dilaksanakan secara individu sambil berkongsi sumber prosesnya.

Apa itu Proses?

Proses pada dasarnya adalah program dalam pelaksanaan. Apabila anda memulakan aplikasi di komputer anda (seperti penyemak imbas atau penyunting teks), sistem operasi membuat proses.

Apa itu Multithreading di Python?

Multithreading dalam pengaturcaraan Python adalah teknik yang terkenal di mana banyak utas dalam proses berkongsi ruang data mereka dengan utas utama yang menjadikan perkongsian maklumat dan komunikasi dalam benang mudah dan efisien. Benang lebih ringan daripada proses. Multi-thread boleh dijalankan secara individu semasa berkongsi sumber prosesnya. Tujuan multithreading adalah menjalankan pelbagai tugas dan sel fungsi pada masa yang sama.

Apa itu Multiprocessing?

Pemprosesan pelbagai membolehkan anda menjalankan beberapa proses yang tidak berkaitan secara serentak. Proses ini tidak berkongsi sumbernya dan berkomunikasi melalui IPC.

Python Multithreading vs Multiprocessing

Untuk memahami proses dan urutan, pertimbangkan senario ini: Fail .exe di komputer anda adalah program. Apabila anda membukanya, OS memuatkannya ke dalam memori, dan CPU melaksanakannya. Contoh program yang sedang berjalan disebut prosesnya.

Setiap proses akan mempunyai 2 komponen asas:

  • Kod tersebut
  • Data itu

Sekarang, proses boleh mengandungi satu atau lebih sub-bahagian yang disebut utas. Ini bergantung pada seni bina OS. Anda boleh memikirkan thread sebagai bahagian proses yang dapat dijalankan secara berasingan oleh sistem operasi.

Dengan kata lain, ini adalah aliran arahan yang dapat dijalankan secara bebas oleh OS. Thread dalam satu proses berkongsi data proses tersebut dan dirancang untuk bekerjasama untuk memfasilitasi paralelisme.

Dalam tutorial ini, anda akan belajar,

  • Apa itu Benang?
  • Apa itu Proses?
  • Apa itu Multithreading?
  • Apa itu Multiprocessing?
  • Python Multithreading vs Multiprocessing
  • Mengapa menggunakan Multithreading?
  • Python MultiThreading
  • Modul Thread dan Threading
  • Modul Thread
  • Modul Threading
  • Kebuntuan dan keadaan Perlumbaan
  • Menyegerakkan benang
  • Apa itu GIL?
  • Mengapa GIL diperlukan?

Mengapa menggunakan Multithreading?

Multithreading membolehkan anda memecah aplikasi menjadi beberapa sub-tugas dan menjalankan tugas ini secara serentak. Sekiranya anda menggunakan multithreading dengan betul, kecepatan, prestasi, dan rendering aplikasi anda semua dapat ditingkatkan.

Python MultiThreading

Python menyokong konstruk untuk proses berbilang proses dan juga multithreading. Dalam tutorial ini, anda akan memfokus pada pelaksanaan aplikasi multithreaded dengan python. Terdapat dua modul utama yang boleh digunakan untuk mengendalikan utas di Python:

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

Walau bagaimanapun, dalam ular sawa, ada juga yang disebut kunci pentafsir global (GIL). Ini tidak memungkinkan peningkatan prestasi yang banyak dan bahkan dapat mengurangkan prestasi beberapa aplikasi multithreaded. Anda akan mengetahui semua perkara di bahagian tutorial ini yang akan datang.

Modul Thread dan Threading

Kedua-dua modul yang anda akan pelajari dalam tutorial ini adalah modul thread dan modul threading .

Walau bagaimanapun, modul utas telah lama tidak digunakan lagi. Bermula dengan Python 3, ia telah ditetapkan sebagai usang dan hanya dapat diakses sebagai __thread untuk keserasian ke belakang.

Anda harus menggunakan modul utas peringkat lebih tinggi untuk aplikasi yang ingin anda gunakan. Modul utas hanya diliputi di sini untuk tujuan pendidikan.

Modul Thread

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

thread.start_new_thread(function_name, arguments)

Baiklah, sekarang anda telah merangkumi teori asas untuk memulakan pengekodan. Oleh itu, buka IDLE atau notepad anda dan taipkan perkara berikut:

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 fail dan tekan F5 untuk menjalankan program. Sekiranya semuanya dilakukan dengan betul, ini adalah output yang harus anda lihat:

Anda akan mengetahui lebih lanjut mengenai keadaan perlumbaan dan cara mengatasinya di bahagian yang akan datang

PENJELASAN KOD

  1. Pernyataan ini mengimport modul masa dan utas yang digunakan untuk menangani pelaksanaan dan kelewatan utas Python.
  2. Di sini, anda telah menentukan fungsi yang disebut thread_test, yang akan dipanggil dengan kaedah start_new_thread . Fungsi menjalankan gelung sementara selama empat lelaran dan mencetak nama utas yang memanggilnya. Setelah lelaran selesai, ia mencetak mesej yang mengatakan bahawa utas telah selesai dijalankan.
  3. Ini adalah bahagian utama program anda. Di sini, anda hanya memanggil kaedah start_new_thread dengan fungsi thread_test sebagai argumen.

    Ini akan membuat utas baru untuk fungsi yang anda lalui sebagai argumen dan mula melaksanakannya. Perhatikan bahawa anda boleh mengganti ini (thread _ test) dengan fungsi lain yang ingin anda jalankan sebagai utas.

Modul Threading

Modul ini adalah pelaksanaan peringkat tinggi dalam python dan standard de facto untuk menguruskan aplikasi multithreaded. Ia menyediakan pelbagai ciri jika dibandingkan dengan modul utas.

Struktur modul Threading

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

Nama Fungsi Penerangan
jumlah aktif () Mengembalikan kiraan objek Thread yang masih hidup
Thread semasa () Mengembalikan objek semasa kelas Thread.
sebutkan () Menyenaraikan semua objek Thread yang aktif.
isDaemon () Kembali benar jika utasnya adalah daemon.
isAlive () Kembali benar jika benang masih hidup.
Kaedah Kelas Thread
mulakan () Memulakan aktiviti utas. Ia mesti dipanggil hanya sekali untuk setiap utas kerana ia akan menimbulkan ralat runtime jika dipanggil berkali-kali.
lari () Kaedah ini menunjukkan aktiviti utas dan boleh diganti oleh kelas yang memanjangkan kelas Thread.
sertai () Ini menyekat pelaksanaan kod lain sehingga utas di mana kaedah bergabung () dipanggil dihentikan.

Latar Belakang: Kelas Thread

Sebelum anda memulakan pengekodan program multithreaded menggunakan modul threading, sangat penting untuk memahami mengenai kelas Thread. Kelas thread adalah kelas utama yang menentukan templat dan operasi thread dalam python.

Cara yang paling biasa untuk membuat aplikasi python multithreaded adalah dengan menyatakan kelas yang memanjangkan kelas Thread dan menggantikan kaedah run ().

Kelas Thread, secara ringkas, menandakan urutan kod yang berjalan dalam yang berasingan thread kawalan.

Oleh itu, semasa menulis aplikasi multithread, anda akan melakukan perkara berikut:

  1. tentukan kelas yang memanjangkan kelas Thread
  2. Mengatasi __init__ pembina
  3. Mengatasi run () kaedah

Setelah objek utas dibuat, kaedah mula () dapat digunakan untuk memulai pelaksanaan kegiatan ini dan kaedah bergabung () dapat digunakan untuk menyekat semua kod lain sehingga kegiatan saat ini selesai.

Sekarang, mari kita cuba menggunakan modul threading untuk melaksanakan contoh sebelumnya. Sekali lagi, aktifkan IDLE anda dan taipkan perkara berikut:

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 apabila anda melaksanakan kod di atas:

PENJELASAN KOD

  1. Bahagian ini sama dengan contoh sebelumnya. Di sini, anda mengimport modul masa dan utas yang digunakan untuk menangani pelaksanaan dan kelewatan utas Python.
  2. Pada masa ini, anda membuat kelas yang dipanggil threadtester, yang mewarisi atau memperluas kelas Thread modul threading. Ini adalah salah satu kaedah yang paling biasa untuk membuat utas di python. Walau bagaimanapun, anda hanya boleh mengatasi kaedah konstruktor dan jalankan () dalam aplikasi anda. Seperti yang anda lihat dalam contoh kod di atas, kaedah __init__ (konstruktor) telah diganti.

    Begitu juga, anda juga telah mengatasi kaedah run () . Ini mengandungi kod yang ingin anda laksanakan di dalam utas. Dalam contoh ini, anda telah memanggil fungsi thread_test ().

  3. Ini adalah kaedah thread_test () yang mengambil nilai i sebagai argumen, menurunkannya sebanyak 1 pada setiap lelaran dan mengulangi kod yang lain sehingga saya menjadi 0. Dalam setiap lelaran, ia mencetak nama utas yang sedang dijalankan dan tidur selama beberapa saat menunggu (yang juga diambil sebagai hujah).
  4. thread1 = threadtester (1, "Thread Pertama", 1)

    Di sini, kami membuat utas dan melewati tiga parameter yang kami nyatakan dalam __init__. Parameter pertama adalah id utas, parameter kedua adalah nama utas, dan parameter ketiga adalah pembilang, yang menentukan berapa kali gelung sementara harus dijalankan.

  5. utas2.start ()

    Kaedah permulaan digunakan untuk memulakan pelaksanaan utas. Secara dalaman, fungsi start () memanggil kaedah run () kelas anda.

  6. utas3.join ()

    Kaedah join () menyekat pelaksanaan kod lain dan menunggu sehingga utas yang disebut selesai.

Seperti yang telah anda ketahui, utas yang dalam proses yang sama mempunyai akses ke memori dan data proses tersebut. Akibatnya, jika lebih dari satu utas cuba mengubah atau mengakses data secara serentak, kesalahan dapat merayap.

Di bahagian seterusnya, anda akan melihat pelbagai jenis komplikasi yang dapat muncul ketika utas mengakses data dan bahagian kritikal tanpa memeriksa transaksi akses yang ada.

Kebuntuan dan keadaan Perlumbaan

Sebelum mengetahui tentang kebuntuan dan keadaan perlumbaan, akan berguna untuk memahami beberapa definisi asas yang berkaitan dengan pengaturcaraan serentak:

  • Bahagian Kritikal

    Ini adalah pecahan kod yang mengakses atau mengubah pemboleh ubah bersama dan mesti dilakukan sebagai transaksi atom.

  • Suis Konteks

    Ini adalah proses yang diikuti oleh CPU untuk menyimpan keadaan utas sebelum menukar dari satu tugas ke tugas yang lain sehingga dapat dilanjutkan dari titik yang sama kemudian.

Kebuntuan

Kebuntuan adalah masalah yang paling ditakuti yang dihadapi oleh pembangun semasa menulis aplikasi serentak / multithreaded di python. Cara terbaik untuk memahami kebuntuan adalah dengan menggunakan masalah contoh sains komputer klasik yang dikenali sebagai Problem Philosophers Problem.

Penyataan masalah bagi ahli falsafah makan adalah seperti berikut:

Lima ahli falsafah duduk di atas meja bulat dengan lima pinggan spageti (sejenis pasta) dan lima garpu, seperti yang ditunjukkan dalam rajah.

Masalah Ahli Falsafah Makan

Pada masa tertentu, seorang ahli falsafah mesti makan atau berfikir.

Lebih-lebih lagi, seorang ahli falsafah mesti mengambil dua garpu yang bersebelahan dengannya (iaitu, garpu kiri dan kanan) sebelum dia dapat memakan spageti. Masalah kebuntuan berlaku apabila kelima-lima ahli falsafah mengambil garpu kanan mereka secara serentak.

Oleh kerana setiap ahli falsafah mempunyai satu garpu, mereka semua akan menunggu yang lain meletakkan garpu mereka. Akibatnya, tidak ada yang dapat makan spageti.

Begitu juga, dalam sistem serentak, kebuntuan berlaku apabila benang atau proses yang berbeza (ahli falsafah) berusaha memperoleh sumber sistem bersama (garpu) pada masa yang sama. Akibatnya, tidak ada proses yang berpeluang untuk dilaksanakan kerana mereka sedang menunggu sumber lain yang dipegang oleh proses lain.

Keadaan Perlumbaan

Keadaan perlumbaan adalah keadaan program yang tidak diingini yang berlaku apabila sistem melakukan dua atau lebih operasi secara serentak. Contohnya, anggap ini mudah untuk gelung

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

Sekiranya anda membuat n bilangan utas yang menjalankan kod ini sekaligus, anda tidak dapat menentukan nilai i (yang dikongsi oleh utas) ketika program selesai dijalankan. Ini kerana dalam persekitaran multithreading yang sebenar, utas dapat bertindih, dan nilai i yang diambil dan diubah oleh utas dapat berubah di antara ketika beberapa utas lain mengaksesnya.

Ini adalah dua kelas masalah utama yang boleh berlaku dalam aplikasi python multithreaded atau diedarkan. Di bahagian seterusnya, anda akan belajar bagaimana mengatasi masalah ini dengan menyegerakkan utas.

Menyegerakkan benang

Untuk menangani keadaan perlumbaan, kebuntuan, dan masalah lain berdasarkan utas, modul utas menyediakan objek Kunci . Ideanya adalah bahawa apabila utas mahukan akses ke sumber tertentu, ia memperoleh kunci untuk sumber itu. Setelah utas mengunci sumber tertentu, tidak ada utas lain yang dapat mengaksesnya sehingga kunci dilepaskan. Akibatnya, perubahan sumber akan menjadi atom, dan keadaan perlumbaan akan dihindari.

Kunci adalah primitif penyegerakan peringkat rendah yang dilaksanakan oleh modul __thread . Pada masa tertentu, kunci boleh berada di salah satu daripada 2 keadaan: terkunci atau tidak dikunci. Ia menyokong dua kaedah:

  1. memperoleh()

    Apabila keadaan kunci tidak dikunci, memanggil kaedah memperoleh () akan mengubah keadaan menjadi terkunci dan kembali. Tetapi, jika keadaan terkunci, panggilan untuk memperoleh () disekat sehingga kaedah pelepasan () dipanggil oleh beberapa utas lain.

  2. pelepasan ()

    Kaedah pelepasan () digunakan untuk mengatur keadaan agar tidak terkunci, iaitu untuk melepaskan kunci. Ia boleh dipanggil oleh benang apa pun, tidak semestinya yang memperoleh kunci.

Berikut adalah contoh penggunaan kunci dalam aplikasi anda. Nyalakan IDLE anda dan taipkan yang berikut:

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 output seperti ini:

PENJELASAN KOD

  1. Di sini, anda hanya membuat kunci baru dengan memanggil fungsi kilang threading.Lock () . Secara dalaman, Lock () mengembalikan contoh kelas Lock konkrit paling berkesan yang dikendalikan oleh platform.
  2. Dalam pernyataan pertama, anda memperoleh kunci dengan memanggil kaedah memperoleh (). Apabila kunci telah diberikan, anda mencetak "kunci diperoleh" ke konsol. Setelah semua kod yang anda mahu utas dijalankan selesai, anda melepaskan kunci dengan memanggil kaedah pelepasan ().

Teorinya baik-baik saja, tetapi bagaimana anda tahu bahawa kunci itu benar-benar berfungsi? Sekiranya anda melihat outputnya, anda akan melihat bahawa setiap pernyataan cetak mencetak tepat satu baris pada satu masa. Ingatlah bahawa, dalam contoh sebelumnya, output dari cetakan di mana tidak sengaja kerana banyak utas mengakses kaedah cetak () pada masa yang sama. Di sini, fungsi cetak dipanggil hanya setelah kunci diperoleh. Jadi, output dipaparkan satu demi satu dan baris demi baris.

Selain kunci, python juga menyokong beberapa mekanisme lain untuk menangani penyegerakan benang seperti yang disenaraikan di bawah:

  1. Rlocks
  2. Semaphores
  3. Syarat
  4. Acara, dan
  5. Halangan

Kunci Jurubahasa Global (dan bagaimana menanganinya)

Sebelum mengetahui perincian GIL python, mari kita tentukan beberapa istilah yang akan berguna dalam memahami bahagian yang akan datang:

  1. Kod terikat CPU: ini merujuk pada sekeping kod yang akan langsung dilaksanakan oleh CPU.
  2. Kod terikat I / O: ini boleh menjadi kod apa pun yang mengakses sistem fail melalui OS
  3. CPython: ini adalah pelaksanaan rujukan Python dan dapat digambarkan sebagai jurubahasa yang ditulis dalam C dan Python (bahasa pengaturcaraan).

Apakah GIL di Python?

Global Interpreter Lock (GIL) dalam python adalah kunci proses atau mutex yang digunakan semasa menangani proses. Ini memastikan bahawa satu utas dapat mengakses sumber tertentu pada satu masa dan juga menghalang penggunaan objek dan kod bytek sekaligus. Ini memberi manfaat kepada program single-thread dalam peningkatan prestasi. GIL di python sangat mudah dan senang dilaksanakan.

Kunci dapat digunakan untuk memastikan bahawa hanya satu utas yang dapat mengakses sumber tertentu pada waktu tertentu.

Salah satu ciri Python ialah ia menggunakan kunci global pada setiap proses pentafsir, yang bermaksud bahawa setiap proses memperlakukan juru bahasa python itu sendiri sebagai sumber.

Sebagai contoh, anggap anda telah menulis program python yang menggunakan dua utas untuk menjalankan operasi CPU dan 'I / O'. Apabila anda menjalankan program ini, inilah yang berlaku:

  1. Jurubahasa python membuat proses baru dan menelurkan benang
  2. Apabila thread-1 mula berjalan, ia akan memperoleh GIL dan menguncinya terlebih dahulu.
  3. Sekiranya thread-2 ingin dijalankan sekarang, ia mesti menunggu GIL dilepaskan walaupun pemproses lain percuma.
  4. Sekarang, anggap thread-1 sedang menunggu operasi I / O. Pada masa ini, ia akan melepaskan GIL, dan thread-2 akan memperolehnya.
  5. Setelah menyelesaikan ops I / O, jika thread-1 ingin dijalankan sekarang, ia sekali lagi harus menunggu GIL dilepaskan oleh thread-2.

Oleh kerana itu, hanya satu utas yang dapat mengakses jurubahasa pada bila-bila masa, yang bermaksud bahawa hanya akan ada satu utas yang melaksanakan kod python pada suatu waktu tertentu.

Ini baik-baik saja dalam pemproses satu teras kerana akan menggunakan pemotongan masa (lihat bahagian pertama tutorial ini) untuk menangani utas. Namun, dalam hal pemproses multi-teras, fungsi terikat CPU yang dijalankan pada banyak utas akan memberikan pengaruh yang besar terhadap kecekapan program kerana sebenarnya tidak akan menggunakan semua inti yang tersedia pada waktu yang sama.

Mengapa GIL diperlukan?

Pengumpul sampah CPython menggunakan teknik pengurusan memori yang cekap yang dikenali sebagai penghitungan rujukan. Begini cara kerjanya: Setiap objek di python mempunyai kiraan rujukan, yang meningkat apabila ditugaskan ke nama pemboleh ubah baru atau ditambahkan ke wadah (seperti tupel, senarai, dll.) Begitu juga, jumlah rujukan dikurangkan ketika rujukan keluar dari ruang lingkup atau ketika pernyataan del dipanggil. Apabila jumlah rujukan objek mencapai 0, sampah dikumpulkan, dan memori yang diberikan dibebaskan.

Tetapi masalahnya ialah pemboleh ubah kiraan rujukan terdedah kepada keadaan perlumbaan seperti pemboleh ubah global yang lain. Untuk menyelesaikan masalah ini, pembangun python memutuskan untuk menggunakan kunci pentafsir global. Pilihan lain adalah dengan menambahkan kunci pada setiap objek yang akan mengakibatkan kebuntuan dan peningkatan overhead dari memperoleh () dan melepaskan () panggilan.

Oleh itu, GIL adalah sekatan yang ketara untuk program python multithreaded yang menjalankan operasi CPU yang berat (berkesan menjadikannya utas tunggal) Sekiranya anda ingin menggunakan beberapa teras CPU dalam aplikasi anda, gunakan modul multiprosesan .

Ringkasan

  • Python menyokong 2 modul untuk multithreading:
    1. Modul __thread : Ini menyediakan pelaksanaan tahap rendah untuk threading dan usang.
    2. threading module : Ia menyediakan pelaksanaan peringkat tinggi untuk multithreading dan merupakan standard semasa.
  • Untuk membuat utas menggunakan modul utas, anda mesti melakukan perkara berikut:
    1. Buat kelas yang memanjangkan kelas Thread .
    2. Tolak pembinaanya (__init__).
    3. Tolak kaedah run () .
    4. Buat objek kelas ini.
  • Benang dapat dijalankan dengan memanggil kaedah start () .
  • Kaedah join () boleh digunakan untuk menyekat utas lain sehingga thread ini (yang di gabungkan dipanggil) selesai dijalankan.
  • Keadaan perlumbaan berlaku apabila beberapa utas mengakses atau mengubah sumber yang dikongsi pada masa yang sama.
  • Ia dapat dielakkan dengan Menyegerakkan benang.
  • Python menyokong 6 cara untuk menyegerakkan utas:
    1. Kunci
    2. Rlocks
    3. Semaphores
    4. Syarat
    5. Acara, dan
    6. Halangan
  • Kunci hanya membenarkan benang tertentu yang memperoleh kunci memasuki bahagian kritikal.
  • A Lock mempunyai 2 kaedah utama:
    1. memperoleh () : Ia menetapkan keadaan kunci menjadi terkunci. Sekiranya dipanggil pada objek yang terkunci, ia menyekat sehingga sumbernya bebas.
    2. pelepasan () : Ia menetapkan keadaan kunci menjadi tidak terkunci dan kembali. Sekiranya dipanggil pada objek yang tidak dikunci, ia akan kembali palsu.
  • Kunci pentafsir global adalah mekanisme di mana hanya 1 proses pentafsir CPython dapat dijalankan pada satu masa.
  • Ia digunakan untuk memudahkan fungsi penghitungan rujukan pengumpul sampah CPythons.
  • Untuk membuat aplikasi Python dengan operasi CPU yang berat, anda harus menggunakan modul multiprosesan.