Apakah kunci juru bahasa global (GIL) dalam CPython?

244

Apa itu kunci juru bahasa global dan mengapa itu menjadi masalah?

Banyak suara telah dibuat sekitar menghapus GIL dari Python, dan saya ingin mengerti mengapa itu sangat penting. Saya belum pernah menulis kompiler atau penerjemah sendiri, jadi jangan hemat dengan detail, saya mungkin perlu mereka mengerti.

e-satis
sumber
3
Tonton David Beazley memberi tahu Anda semua yang ingin Anda ketahui tentang GIL.
hughdbrown
1
Inilah artikel gondrong yang membicarakan GIL dan threading dengan Python yang saya tulis beberapa waktu lalu. Ini masuk ke dalam jumlah yang cukup detail di atasnya: jessenoller.com/2009/02/01/…
jnoller
Berikut adalah beberapa kode yang menunjukkan efek GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu
3
Saya menemukan ini adalah penjelasan terbaik tentang GIL. Silakan baca. dabeaz.com/python/UnderstandingGIL.pdf
suhao399
realpython.com/python-gil Saya menemukan ini berguna
qwr

Jawaban:

220

GIL Python dimaksudkan untuk membuat serial akses ke interpreter internals dari utas yang berbeda. Pada sistem multi-core, ini berarti bahwa beberapa utas tidak dapat secara efektif memanfaatkan banyak inti. (Jika GIL tidak mengarah ke masalah ini, kebanyakan orang tidak akan peduli dengan GIL - itu hanya diangkat sebagai masalah karena meningkatnya prevalensi sistem multi-inti.) Jika Anda ingin memahaminya secara rinci, Anda dapat melihat video ini atau melihat kumpulan slide ini . Mungkin terlalu banyak informasi, tetapi kemudian Anda memang meminta detail :-)

Perhatikan bahwa GIL Python hanya benar-benar masalah untuk CPython, implementasi referensi. Jython dan IronPython tidak memiliki GIL. Sebagai pengembang Python, Anda biasanya tidak menemukan GIL kecuali Anda sedang menulis ekstensi C. Penulis ekstensi C perlu melepaskan GIL ketika ekstensi mereka memblokir I / O, sehingga utas lain dalam proses Python mendapatkan kesempatan untuk berjalan.

Vinay Sajip
sumber
46
Jawaban yang bagus - pada dasarnya itu berarti bahwa utas dalam Python hanya baik untuk memblokir I / O; aplikasi Anda tidak akan pernah pergi di atas 1 inti CPU penggunaan prosesor
Ana Betts
8
"Sebagai pengembang Python, Anda biasanya tidak menemukan GIL kecuali Anda sedang menulis ekstensi C" - Anda mungkin tidak tahu bahwa penyebab kode multi-ulir Anda berjalan dengan kecepatan siput adalah GIL, tetapi Anda Saya pasti akan merasakan efeknya. Masih mengherankan saya bahwa untuk mengambil keuntungan dari server 32-core dengan Python berarti saya perlu 32 proses dengan semua overhead yang terkait.
Dasar
6
@ PaulBetts: itu tidak benar. Sangat mungkin bahwa kinerja kode kritis sudah menggunakan C ekstensi yang dapat dan jangan melepaskan GIL misalnya, regex, lxml, numpymodul. Cython memungkinkan untuk melepaskan GIL dalam kode khusus misalnya,b2a_bin(data)
jfs
5
@ Paul Betts: Anda bisa mendapatkan di atas 1 kode CPU penggunaan prosesor menggunakan modul multiprosesor . Membuat banyak proses adalah "lebih berat" daripada membuat banyak utas, tetapi jika Anda benar-benar perlu menyelesaikan pekerjaan secara paralel, dengan python, itu adalah sebuah pilihan.
AJNeufeld
1
@david_adler Ya, masih demikian, dan kemungkinan akan tetap begitu untuk sementara waktu. Itu tidak benar-benar menghentikan Python menjadi sangat berguna untuk banyak beban kerja yang berbeda.
Vinay Sajip
59

Misalkan Anda memiliki banyak utas yang tidak benar - benar menyentuh data satu sama lain. Mereka harus mengeksekusi sebebas mungkin. Jika Anda memiliki "kunci global" yang perlu Anda peroleh untuk (katakanlah) memanggil fungsi, itu bisa berakhir sebagai hambatan. Anda bisa mendapatkan banyak manfaat dengan memiliki banyak utas.

Untuk membuatnya menjadi analogi dunia nyata: bayangkan 100 pengembang bekerja di sebuah perusahaan dengan hanya satu cangkir kopi. Sebagian besar pengembang akan menghabiskan waktu menunggu kopi alih-alih menulis kode.

Tak satu pun dari ini adalah Python-spesifik - Saya tidak tahu rincian apa yang dibutuhkan Python untuk GIL di tempat pertama. Namun, semoga ini memberi Anda ide yang lebih baik dari konsep umum.

Jon Skeet
sumber
Kecuali menunggu cangkir kopi sepertinya proses I / O terikat, karena mereka pasti dapat melakukan hal-hal lain sambil menunggu cangkir. GIL memiliki efek yang sangat kecil pada benang berat I / O yang menghabiskan sebagian besar waktu mereka menunggu.
Cruncher
36

Pertama mari kita memahami apa yang disediakan oleh python GIL:

Setiap operasi / instruksi dieksekusi dalam juru bahasa. GIL memastikan bahwa penerjemah dipegang oleh satu utas pada saat tertentu . Dan program python Anda dengan banyak utas berfungsi dalam satu juru bahasa. Pada saat tertentu, juru bahasa ini dipegang oleh satu utas. Ini berarti bahwa hanya benang yang memegang interpreter yang berjalan di sembarang waktu .

Sekarang mengapa itu menjadi masalah:

Mesin Anda dapat memiliki beberapa inti / prosesor. Dan beberapa core memungkinkan banyak thread untuk dieksekusi secara bersamaan yaitu beberapa thread dapat dieksekusi pada saat tertentu. . Tetapi karena penerjemah dipegang oleh satu utas, utas lainnya tidak melakukan apa pun meskipun mereka memiliki akses ke inti. Jadi, Anda tidak mendapatkan keuntungan apa pun yang disediakan oleh beberapa inti karena pada saat apa pun hanya satu inti, yang merupakan inti yang digunakan oleh utas yang saat ini memegang juru bahasa, sedang digunakan. Jadi, program Anda akan membutuhkan waktu lama untuk dieksekusi seolah-olah itu adalah program berulir tunggal.

Namun, operasi yang berpotensi memblokir atau berjalan lama, seperti I / O, pemrosesan gambar, dan angka NumPy, terjadi di luar GIL. Diambil dari sini . Jadi untuk operasi seperti itu, operasi multithreaded akan tetap lebih cepat daripada operasi threaded tunggal meskipun ada GIL. Jadi, GIL tidak selalu menjadi hambatan.

Sunting: GIL adalah detail implementasi CPython. IronPython dan Jython tidak memiliki GIL, jadi program yang benar-benar multithreaded mungkin ada di dalamnya, pikir saya belum pernah menggunakan PyPy dan Jython dan tidak yakin akan hal ini.

Akshar Raaj
sumber
4
Catatan : PyPy memiliki GIL . Referensi : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . Sementara Ironpython dan Jython tidak memiliki GIL.
Tasdik Rahman
Memang, PyPy memiliki GIL, tetapi IronPython tidak.
Emmanuel
@Emmanuel Mengedit jawaban untuk menghapus PyPy dan memasukkan IronPython.
Akshar Raaj
17

Python tidak mengizinkan multi-threading dalam arti kata yang sebenarnya. Ini memiliki paket multi-threading tetapi jika Anda ingin multi-thread untuk mempercepat kode Anda, maka itu biasanya bukan ide yang baik untuk menggunakannya. Python memiliki konstruk yang disebut Global Interpreter Lock (GIL).

https://www.youtube.com/watch?v=ph374fJqFPE

GIL memastikan bahwa hanya satu dari 'utas' Anda yang dapat dieksekusi pada satu waktu. Sebuah thread mendapatkan GIL, melakukan sedikit pekerjaan, lalu meneruskan GIL ke thread berikutnya. Hal ini terjadi sangat cepat sehingga bagi mata manusia, sepertinya utas Anda berjalan paralel, tetapi mereka benar-benar hanya bergantian menggunakan inti CPU yang sama. Semua operan GIL ini menambah biaya eksekusi. Ini berarti bahwa jika Anda ingin membuat kode Anda berjalan lebih cepat maka menggunakan paket threading sering bukan ide yang baik.

Ada alasan untuk menggunakan paket threading Python. Jika Anda ingin menjalankan beberapa hal secara bersamaan, dan efisiensi bukan masalah, maka itu benar-benar baik dan nyaman. Atau jika Anda menjalankan kode yang perlu menunggu sesuatu (seperti IO) maka itu bisa masuk akal. Tetapi pustaka threading tidak akan membiarkan Anda menggunakan core CPU tambahan.

Multi-threading dapat dialihdayakan ke sistem operasi (dengan melakukan multi-pemrosesan), beberapa aplikasi eksternal yang memanggil kode Python Anda (misalnya, Spark atau Hadoop), atau beberapa kode yang panggilan kode Python Anda (misalnya: Anda dapat memiliki Python Anda panggilan kode fungsi C yang melakukan hal-hal multi-threaded mahal).

Ijaz Ahmad Khan
sumber
15

Setiap kali dua utas memiliki akses ke variabel yang sama Anda memiliki masalah. Dalam C ++ misalnya, cara untuk menghindari masalah adalah dengan mendefinisikan beberapa kunci mutex untuk mencegah dua utas, katakanlah, masukkan penyetel objek pada saat yang sama.

Multithreading dimungkinkan dalam python, tetapi dua utas tidak dapat dieksekusi pada saat yang sama pada granularity yang lebih baik daripada satu instruksi python. Utas yang menjalankan mendapatkan kunci global yang disebut GIL.

Ini berarti jika Anda mulai menulis beberapa kode multithread untuk memanfaatkan prosesor multicore Anda, kinerja Anda tidak akan membaik. Solusi yang biasa terdiri dari proses multiproses.

Perhatikan bahwa dimungkinkan untuk melepaskan GIL jika Anda menggunakan metode yang Anda tulis dalam C misalnya.

Penggunaan GIL tidak melekat pada Python tetapi untuk beberapa penerjemahnya, termasuk CPython yang paling umum. (#edited, lihat komentar)

Masalah GIL masih valid dalam Python 3000.

fulmicoton
sumber
Stackless masih memiliki GIL. Stackless tidak meningkatkan threading (seperti pada modul) - ia menawarkan metode pemrograman yang berbeda (coroutine) yang mencoba untuk mengesampingkan masalah, tetapi membutuhkan fungsi-fungsi yang tidak menghalangi.
jnoller
Bagaimana dengan GIL baru di 3.2?
new123456
Hanya untuk menambahkan bahwa Anda tidak memiliki masalah / perlu mutex / semaphores jika hanya satu utas yang akan memperbarui memori. @ new123456 ini mengurangi pertikaian dan menjadwalkan utas lebih baik tanpa menyakiti kinerja utas tunggal (yang mengesankan dalam dirinya sendiri) tetapi itu masih merupakan kunci global.
Dasar
14

Dokumentasi Python 3.7

Saya juga ingin menyoroti kutipan berikut dari dokumentasi Pythonthreading :

Detail implementasi CPython: Di CPython, karena Global Interpreter Lock, hanya satu utas yang dapat mengeksekusi kode Python sekaligus (meskipun pustaka yang berorientasi kinerja tertentu mungkin mengatasi batasan ini). Jika Anda ingin aplikasi Anda memanfaatkan sumber daya komputasi mesin multi-core dengan lebih baik, Anda disarankan untuk menggunakan multiprocessingatau concurrent.futures.ProcessPoolExecutor. Namun, threading masih merupakan model yang tepat jika Anda ingin menjalankan beberapa tugas yang terikat I / O secara bersamaan.

Tautan ini ke entri Glosariumglobal interpreter lock yang menjelaskan bahwa GIL menyiratkan bahwa paralelisme berulir dalam Python tidak cocok untuk tugas yang terikat CPU :

Mekanisme yang digunakan oleh juru bahasa CPython untuk memastikan bahwa hanya satu utas yang mengeksekusi bytecode Python sekaligus. Ini menyederhanakan implementasi CPython dengan membuat model objek (termasuk tipe bawaan yang kritis seperti dict) secara implisit aman terhadap akses bersamaan. Mengunci seluruh juru bahasa membuatnya lebih mudah bagi juru bahasa untuk menjadi multi-threaded, dengan mengorbankan banyak paralelisme yang diberikan oleh mesin multi-prosesor.

Namun, beberapa modul ekstensi, baik standar atau pihak ketiga, dirancang untuk melepaskan GIL saat melakukan tugas-tugas yang intensif secara komputasi seperti kompresi atau hashing. Juga, GIL selalu dirilis saat melakukan I / O.

Upaya masa lalu untuk menciptakan juru bahasa "bebas-ulir" (yang mengunci data bersama pada granularitas yang jauh lebih halus) belum berhasil karena kinerja menderita dalam kasus prosesor tunggal yang umum. Diyakini bahwa mengatasi masalah kinerja ini akan membuat implementasi jauh lebih rumit dan oleh karena itu lebih mahal untuk dipertahankan.

Kutipan ini juga menyiratkan bahwa dikt dan karenanya penugasan variabel juga aman sebagai detail implementasi CPython:

Selanjutnya, dokumen untuk multiprocessingpaket menjelaskan bagaimana mengatasi GIL dengan proses pemijahan sambil mengekspos antarmuka yang mirip dengan threading:

multiprocessing adalah paket yang mendukung proses pemijahan menggunakan API yang mirip dengan modul threading. Paket multi-pemrosesan menawarkan konkurensi lokal dan jarak jauh, secara efektif menuntun Global Interpreter Lock dengan menggunakan subproses alih-alih utas. Karena ini, modul multiprosesing memungkinkan programmer untuk memanfaatkan sepenuhnya beberapa prosesor pada mesin yang diberikan. Ini berjalan di kedua Unix dan Windows.

Dan dokumen untukconcurrent.futures.ProcessPoolExecutor menjelaskan itu digunakan multiprocessingsebagai backend:

Kelas ProcessPoolExecutor adalah subkelas Pelaksana yang menggunakan kumpulan proses untuk mengeksekusi panggilan secara tidak sinkron. ProcessPoolExecutor menggunakan modul multiprocessing, yang memungkinkannya untuk melakukan side-step Lock Global Interpreter tetapi juga berarti bahwa hanya objek yang dapat di-pickable yang dapat dieksekusi dan dikembalikan.

yang harus dikontraskan dengan kelas dasar lainnya ThreadPoolExecutoryang menggunakan utas bukannya proses

ThreadPoolExecutor adalah subkelas Pelaksana yang menggunakan kumpulan utas untuk melakukan panggilan secara tidak sinkron.

dari mana kami menyimpulkan bahwa ThreadPoolExecutorhanya cocok untuk tugas terikat I / O, sementara ProcessPoolExecutorjuga dapat menangani tugas terikat CPU.

Pertanyaan berikut menanyakan mengapa GIL ada di tempat pertama: Mengapa Global Interpreter Lock?

Percobaan proses vs utas

Di Multiprocessing vs Threading Python saya telah melakukan analisis eksperimental proses vs threads di Python.

Pratinjau cepat hasil:

masukkan deskripsi gambar di sini

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
0

Mengapa Python (CPython dan lainnya) menggunakan GIL

Dari http://wiki.python.org/moin/GlobalInterpreterLock

Dalam CPython, kunci juru bahasa global, atau GIL, adalah mutex yang mencegah beberapa utas asli mengeksekusi bytecode Python sekaligus. Kunci ini diperlukan terutama karena manajemen memori CPython tidak aman untuk thread.

Bagaimana cara menghapusnya dari Python?

Seperti Lua, mungkin Python dapat memulai banyak VM, Tapi python tidak melakukan itu, saya kira harus ada beberapa alasan lain.

Dalam Numpy atau pustaka diperluas python lainnya, kadang-kadang, melepaskan GIL ke utas lain dapat meningkatkan efisiensi seluruh program.

Maoyang
sumber
0

Saya ingin berbagi contoh dari buku multithreading untuk Efek Visual. Jadi di sini adalah situasi kunci mati klasik

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Sekarang pertimbangkan kejadian dalam urutan yang menghasilkan dead-lock.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    Main Thread                             Other Thread                         
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
 1  Python Command acquires GIL             Work started                         
 2  Computation requested                   MyCallback runs and acquires MyMutex 
 3                                          MyCallback now waits for GIL         
 4  MyCallback runs and waits for MyMutex   waiting for GIL                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
pengguna1767754
sumber