Как проще всего избежать ограничений GIL? | OTUS
⚡ Открываем подписку на курсы!
Проходите параллельно 3 онлайн-курса в месяц по цене одного.
Подробнее

Курсы

Программирование
iOS Разработчик. Продвинутый курс Программист 1С Реверс-инжиниринг. Продвинутый курс
-16%
Java Developer. Professional
-17%
JavaScript Developer. Professional
-18%
Flutter Mobile Developer
-15%
MS SQL Server Developer
-14%
Unity Game Developer. Basic
-19%
Супер-практикум по использованию и настройке GIT
-18%
Супер-интенсив "СУБД в высоконагруженных системах"
-18%
Web-разработчик на Python
-11%
Backend-разработчик на PHP
-8%
PostgreSQL
-10%
Базы данных
-19%
Android-разработчик. Базовый курс Разработчик Python. Продвинутый курс Разработчик на Spring Framework AWS для разработчиков Cloud Solution Architecture CI/CD Vue.js разработчик Разработчик Node.js Scala-разработчик Супер - интенсив по Kubernetes Symfony Framework Advanced Fullstack JavaScript developer
Специализации Курсы в разработке Подготовительные курсы
+7 499 938-92-02

Как проще всего избежать ограничений GIL?

Python_Deep_8-5020-a10ed2.10_site.png

Многие из вас знают, что в Python есть GILGlobal Interpreter Lock, тот самый, который не даёт запускать несколько потоков и нагружать ядра процессора. Отчасти это так, но за GIL в Python скрывается очень много всего. И вот несколько фактов о нём:

GIL будет всегда. Но это не точно. — Захват GIL – одна из первых инструкций, которая выполняется в начале работы вашего кода. — Блокировка переключается каждые 100 инструкций байт-кода до версии 3.2. — Однако в Python до версии 3.2 у нас была возможность управлять этой цифрой. После – нет. — Всё потому, что в Python 3.2 и выше GIL был очень сильно переписан. Теперь блокировка переключается по времени. — Потоки соревнуются за захват GIL, у некоторых из них, например, тех, которые активно занимаются вводом\выводом, это получается чаще. — До версии 3.2 есть тонкости обработки сигналов при многопоточном коде – ваш код иногда может не получить сигнал от ОС. — Планированием выполнения потоков занимается ОС, а Python только говорит ей, как ему бы хотелось. — Вы можете обойти ограничения в своём коде при помощи multiprocessing. — Или написав своё Python C Extension.

Рассмотрим один из самых очевидных способов избежать ограничений GIL’а при выполнении CPU-intensive задач.

Модуль multiprocessing

Запускай по процессу на ядро и вперёд! К сожалению, очевидный способ не значит, что простой. Допустим, запускается процесс, который fork’ает дочерний процесс, но перед этим загружает в память какой-нибудь большой read-only кэш, необходимый этим процессам для функционирования.

Вот это класс! Казалось бы, за счёт copy-on-write (предполагаем Linux) оба процесса будут видеть один и тот же кэш, но дублировать его в памяти не придётся. В top (ну или ps) увидим, что у процессов RSS (Resident Set Size) меньше VMS (Virtual Memory Size). А если кто-то из них и решит туда записать, то ОС скопирует в его адресное пространство только нужные страницы.

Всё так, но есть нюансы

В Python управление памятью осуществляется с помощью reference counting. И даже простой цикл по списку, например, вызывает увеличение счётчика ссылок находящихся в нём объектов. То есть, скорее всего, наш большой кэш не останется надолго в общей памяти, а быстро скопируется в адресные пространства процессов.

Чтобы этого избежать нужно положить кэш в shared memory, то есть разделяемую память, которая является общей для обоих процессов. Multiprocessing даёт нам такую возможность, но выбор опций достаточно скудный: можно создать одно значение или массив. Для чего-то более сложного придётся использовать mmap, но программировать это будет ещё сложнее.

Есть вопрос? Напишите в комментариях!

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться