Що таке блокування глобального перекладача (GIL) у CPython?


244

Що таке блокування глобального перекладача і чому це питання?

Багато шуму було видано навколо видалення GIL з Python, і я хотів би зрозуміти, чому це так важливо. Я ніколи не писав ні компілятора, ні перекладача, тому не будьте обережні з деталями, я, мабуть, потребую їх розуміння.


3
Дивіться, як Девід Бізлі розповість вам все, що хотіли дізнатися про GIL.
hughdbrown

1
Ось довга стаття, що розповідає про GIL та нарізування в Python, яку я написав певний час. Про це йдеться в деталях: jessenoller.com/2009/02/01/…
jnoller

Ось код, що демонструє ефекти GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu

3
Я вважаю, що це найкраще пояснення GIL. Будь ласка, прочитайте. dabeaz.com/python/UnderstandingGIL.pdf
suhao399

realpython.com/python-gil Я вважаю це корисним
qwr

Відповіді:


220

GIL Python призначений для серіалізації доступу до інтернету інтерпретатора з різних потоків. У багатоядерних системах це означає, що кілька потоків не можуть ефективно використовувати кілька ядер. (Якщо GIL не призвела до цієї проблеми, більшість людей не піклується про GIL - це виникає лише як проблема через все більшу поширеність багатоядерних систем.) Якщо ви хочете зрозуміти це докладно, ви можете переглянути це відео або переглянути цей набір слайдів . Це може бути занадто багато інформації, але тоді ви запитали деталі :-)

Зауважте, що GIL Python - це справді проблема лише для CPython, референсної реалізації. Jython та IronPython не мають GIL. Як розробник Python, ви зазвичай не натрапляєте на GIL, якщо не пишете розширення C. Автори розширень C повинні випустити GIL, коли їх розширення блокують введення-виведення, щоб інші потоки в процесі Python отримували шанс запуститися.


46
Хороша відповідь - це означає, що потоки в Python корисні лише для блокування вводу / виводу; ваш додаток ніколи не перевищить 1 процесор ядро ​​використання процесора
Ана Беттс

8
"Як розробник Python, ви зазвичай не натрапляєте на GIL, якщо ви не пишете розширення C" - Ви, можливо, не знаєте, що причиною вашого багатопотокового коду, який працює зі слимаком, є GIL, але ви ' Ви обов'язково відчуєте його наслідки. Мене все ще дивує, що щоб скористатися 32-ядерним сервером з Python, значить, мені потрібні 32 процеси з усіма пов'язаними накладними витратами.
Основні

6
@PaulBetts: це неправда. Цілком ймовірно , що продуктивність критично код вже використовує розширення C , які можуть і не випустити Gil , наприклад, regex, lxml, numpyмодулі. Cython дозволяє випустити GIL у користувацькому коді, наприклад,b2a_bin(data)
jfs

5
@Paul Betts: Ви можете отримати понад 1 CPU код використання процесора за допомогою багатопроцесорного модуля. Створення декількох процесів - «більша вага», ніж створення декількох потоків, але якщо вам справді потрібно виконати роботу паралельно, в python, це варіант.
AJNeufeld

1
@david_adler Так, все ще так, і, ймовірно, так і залишиться ще деякий час. Це насправді не зупинило Python бути дуже корисним для багатьох різних навантажень.
Vinay Sajip

59

Припустимо, у вас є кілька потоків, які насправді не торкаються даних один одного. Вони повинні виконуватись наскільки це можливо незалежно. Якщо у вас є "глобальний замок", який вам потрібно придбати, щоб (сказати) викликати функцію, це може закінчитися як вузьке місце. Ви можете закінчитись, не отримуючи великої користі від того, що в першу чергу є кілька ниток.

Щоб перетворити це на реальну світову аналогію: уявіть, що 100 розробників працюють у компанії лише з однією кружкою кави. Більшість розробників витратили час на очікування кави, а не кодування.

Ніщо з цього не стосується Python - я не знаю, в першу чергу, для чого Python потрібен GIL. Однак, сподіваємось, це дало вам краще уявлення про загальну концепцію.


За винятком того, що очікування кавової кружки виглядає як досить пов'язаний процес введення-виводу, оскільки вони, безперечно, можуть робити інші речі, поки чекаєте кухлі. GIL дуже мало впливає на важкі потоки вводу / виводу, які все одно проводять більшу частину свого часу в очікуванні.
Cruncher


36

Давайте спочатку розберемося, що забезпечує пітон GIL:

Будь-яка операція / інструкція виконується в перекладачі. GIL забезпечує, що інтерпретатор утримується однією ниткою в певний момент часу . І ваша програма python з декількома потоками працює в одному інтерпретаторі. У будь-який момент часу цей перекладач утримується однією ниткою. Це означає, що в будь-який момент часу працює тільки нитка, яку тримає перекладач .

Тепер, чому це питання:

Ваша машина може мати декілька ядер / процесорів. І кілька ядер дозволяють декільком потокам виконувати одночасно тобто кілька потоків можуть виконуватися в будь-який конкретний момент часу. . Але оскільки інтерпретатор утримується однією ниткою, інші потоки нічого не роблять, хоча вони мають доступ до ядра. Таким чином, ви не отримуєте жодної переваги, що надається декількома ядрами, оскільки в будь-який момент використовується лише одне ядро, яке є ядром, використовуваним потоком, який зараз містить інтерпретатор. Отже, ваша програма займе стільки ж часу, як ніби це була одна потокова програма.

Однак потенційно блокуючі або тривалі операції, такі як введення / виведення, обробка зображення та стиснення числа NumPy, відбуваються поза GIL. Взято звідси . Тож для таких операцій багатопотокова операція все одно буде швидшою, ніж одна потокова операція, незважаючи на наявність GIL. Отже, GIL - це не завжди вузьке місце.

Редагувати: GIL - це деталізація реалізації CPython. У IronPython та Jython немає GIL, тому в них повинна бути можлива справді багатопотокова програма, подумав, що я ніколи не використовував PyPy та Jython і не впевнений у цьому.


4
Примітка : PyPy має GIL . Довідка : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . У той час як Ironpython і Jython не мають GIL.
Тасдік Рахман

Дійсно, PyPy має GIL, але IronPython цього не робить.
Еммануїл

@Emmanuel Редагував відповідь, щоб видалити PyPy і включити IronPython.
Акшар Раай

17

Python не дозволяє проводити багаторядну передачу в прямому сенсі цього слова. У нього є пакет з декількома нитками, але якщо ви хочете скористатись декількома нитками, щоб пришвидшити свій код, зазвичай використовувати його не годиться. У Python є конструкція під назвою Global Interpreter Lock (GIL).

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

GIL гарантує, що лише один із ваших "потоків" може виконатись будь-коли. Нитка набуває GIL, виконує невелику роботу, потім передає GIL на наступну нитку. Це відбувається дуже швидко, тому для людського ока може здатися, що ваші потоки виконуються паралельно, але вони насправді просто по черзі, використовуючи те саме ядро ​​CPU. Все це проходження GIL додає накладних витрат на виконання. Це означає, що якщо ви хочете змусити свій код запустити швидше, то використання пакету для нарізки часто не є хорошою ідеєю.

Є причини використовувати пакет для нарізки Python. Якщо ви хочете виконувати деякі речі одночасно, а ефективність не викликає особливих проблем, то це абсолютно добре і зручно. Або якщо ви працюєте з кодом, який потребує чогось чекати (наприклад, якийсь IO), це може мати багато сенсу. Але бібліотека з ниткою не дозволить вам використовувати додаткові ядра CPU.

Багатопотокове передавання можна передавати в операційну систему (виконуючи мультиобробку), якусь зовнішню програму, яка викликає ваш код Python (наприклад, Spark або Hadoop), або якийсь код, який викликає ваш код Python (наприклад, у вас може бути ваш Python виклик коду функцією C, яка виконує дорогі багатопотокові речі).


15

Кожен раз, коли два потоки мають доступ до однієї змінної, у вас виникає проблема. Наприклад, у C ++ спосіб уникнути проблеми полягає у визначенні деякого блокування mutex, щоб запобігти двом потокам, щоб, скажімо, одночасно ввести сеттер об'єкта.

Багатопотокове читання можливе в python, але два потоки не можуть бути виконані одночасно із деталізацією, тоншою, ніж одна інструкція python. Запуск потоку отримує глобальний замок під назвою GIL.

Це означає, що якщо ви почнете писати якийсь багатопотоковий код, щоб скористатися багатоядерним процесором, ваша продуктивність не покращиться. Звичайний спосіб вирішення складається з багатопроцесорної роботи.

Зверніть увагу, що GIL можна випустити, якщо ви, наприклад, використовуєте метод, який ви написали на C.

Використання GIL не властиве Python, а деякому його інтерпретатору, включаючи найпоширеніший CPython. (# редагував, дивись коментар)

Випуск GIL досі діє в Python 3000.


Безрезультатний досі має GIL. Stackless не покращує нарізання різьби (як у модулі) - він пропонує інший метод програмування (спрощення), які намагаються перейти на іншу проблему, але вимагають неблокуючих функцій.
jnoller

Що з новим GIL в 3.2?
new123456

Просто додамо, що у вас немає проблеми / потрібні mutexes / semaphores, якщо лише один потік оновить пам'ять. @ new123456 він зменшує суперечки і планує потоки краще, не завдаючи шкоди однопотоковій продуктивності (що саме по собі вражає), але все-таки глобальним блокуванням.
Основні

14

Документація на Python 3.7

Я також хотів би виділити наступну цитату з документації Pythonthreading :

Деталі реалізації CPython: У CPython, завдяки глобальному блоку інтерпретаторів, лише один потік може виконувати код Python одночасно (навіть якщо певні бібліотеки, орієнтовані на ефективність, можуть подолати це обмеження). Якщо ви хочете, щоб ваша програма краще використовувала обчислювальні ресурси багатоядерних машин, вам рекомендується використовувати multiprocessingабо concurrent.futures.ProcessPoolExecutor. Однак нарізка різьби все ще є відповідною моделлю, якщо ви хочете виконувати кілька завдань, пов'язаних з входом / виводом одночасно.

Це посилання на запис Глосарію, вglobal interpreter lock якому пояснюється, що GIL означає, що потоковий паралелізм у Python не підходить для завдань, пов'язаних з процесором :

Механізм, що використовується інтерпретатором CPython, щоб переконатися, що лише один потік виконує байт-код Python одночасно. Це спрощує реалізацію CPython, роблячи об'єктну модель (включаючи критичні вбудовані типи, такі як dict), неявно захищеними від паралельного доступу. Блокування всього перекладача полегшує багатотрубну перекладач за рахунок більшої частини паралелізму, який надають багатопроцесорні машини.

Однак деякі модулі розширення, стандартні або сторонні, розроблені так, щоб звільнити GIL при виконанні обчислювально-інтенсивних завдань, таких як стиснення або хешування. Крім того, GIL завжди вивільняється при виконанні вводу / виводу.

Минулі зусилля по створенню інтерпретатора «з вільною ниткою» (той, який фіксує спільні дані на значно більш детальну деталізацію) не мали успіху, оскільки продуктивність постраждала в загальному випадку з одним процесором. Вважається, що подолання цього питання ефективності зробить реалізацію значно складнішою, а отже, і дорогішою у підтримці.

Ця цитата також передбачає, що дикти та таким чином присвоєння змінних також є безпечними для потоків як деталі реалізації CPython:

Далі, документи для multiprocessingпакета пояснюють, як він долає GIL шляхом нерестування під час викриття інтерфейсу, аналогічного інтерфейсу threading:

багатопроцесорна робота - це пакет, який підтримує нерестові процеси, використовуючи API, подібний модулю нарізки. Багатопроцесорний пакет пропонує як локальну, так і віддалену одночасність, ефективно наступаючи на глобальний блокнот інтерпретатора, використовуючи підпроцеси замість ниток. Завдяки цьому багатопроцесорний модуль дозволяє програмісту повністю використовувати декілька процесорів на даній машині. Він працює як на Unix, так і на Windows.

І документи дляconcurrent.futures.ProcessPoolExecutor пояснення, що він використовується multiprocessingяк бекенд:

Клас ProcessPoolExecutor - це підклас Executor, який використовує пул процесів для асинхронного виконання викликів. ProcessPoolExecutor використовує мультипроцесорний модуль, що дозволяє йому здійснювати бічний крок глобального блоку інтерпретаторів, але також означає, що виконувати та повертати можна лише об'єкти, що вибираються.

що має протиставлятися іншому базовому класу, ThreadPoolExecutorякий використовує потоки замість процесів

ThreadPoolExecutor - це підклас Executor, який використовує пул потоків для асинхронного виконання викликів.

з якого ми робимо висновок, що ThreadPoolExecutorвін підходить лише для завдань, пов'язаних з входом / виводом, а ProcessPoolExecutorтакож може обробляти завдання, пов'язані з процесором.

Наступне запитання задає питання, чому GIL існує в першу чергу: Чому блокування глобального перекладача?

Експерименти з процесами та потоками

У програмі Multiprocessing vs Threading Python я провів експериментальний аналіз процесу проти потоків у Python.

Швидкий попередній перегляд результатів:

введіть тут опис зображення


0

Чому Python (CPython та інші) використовує GIL

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

У CPython глобальне блокування інтерпретатора або GIL - це мютекс, який запобігає виконанню декількох нативних потоків одночасно байткодами Python. Цей замок необхідний головним чином, оскільки управління пам'яттю CPython не є безпечним для потоків.

Як видалити його з Python?

Як і Lua, можливо, Python міг би запустити кілька VM, але python цього не робить, я думаю, повинні бути деякі інші причини.

У Numpy чи іншій розширеній бібліотеці python іноді вивільнення GIL в інші потоки може підвищити ефективність всієї програми.


0

Я хочу поділитися прикладом із багатопотокової книги для візуальних ефектів. Тож ось класична ситуація з мертвим замком

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

Тепер розглянемо події в тій послідовності, що призводить до безвихідності.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    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                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.