LINUX.ORG.RU

Многопоточный питон, PEP 554

 ,


1

3

Как вы могли знать, уже два года идет вялотекущий перевод питона на поддержку независимых интерпретаторов в одном процессе.

https://github.com/ericsnowcurrently/multi-core-python
https://www.python.org/dev/peps/pep-0554/

По ходу дела всплывают спящие баги, которые были в интерпретаторе годами, но их никто не исправлял, потому что они не проявлялись до этого. Постепенно глобальные переменные в интерпретаторе вычищаются вилкой - можно ожидать, что через годик-другой реализацией независимых интерпретаторов в одном процессе можно будет пользоваться.

Теперь главный вопрос: зачем? PEP 554 предлагает обращаться за помощью к каналам, как в Go, но:
- каналы как средство взаимодействия потоков убоги даже в Go;
- в питоне уже есть multiprocessing, который по сути и есть развитая идея каналов с возможностью прокинуть в обе стороны вызов функции и запрос атрибутов объекта, вплоть до итераторов, но не более того.

Для людей, которые уже готовы отвечать «ну дык есть же многопроцессы, есть форк на никсах - вот и используйте их», напомню, что реализация форка процесса на уровне ОС-и в действительности мало помогает многоПРОЦЕССовости питона - машем ручкой сборщику мусора, который передергивает счетчики ссылок:
https://instagram-engineering.com/dismissing-python-garbage-collection-at-ins...

>>> import os, sys, gc
>>> len(gc.get_objects())
6888
Это весьма эффективная модель сборки мусора... если у вас ровно один процесс.

Допустим, у нас уже есть несколько независимых потоков интерпретатора. Логичное желание - придумать какие-то эффективные модели взаимодействия интерпретаторов, которыми кто-то захочет пользоваться.
Примитивные ячейки данных (вроде многопоточного bytearray или других настраиваемых структур) довольно бесполезны, потому что мы же пользуемся питоном для того, чтобы взмыть вверх по абстракциям. Если же мы надумаем передать через подобнюу структуру хотя бы элементарную функцию, вроде

def hello():
    return "hello world"
то окажется, что объект строки и объект функции сидят в другом интерпретаторе, а сборщик мусора принципиально не умеет работать более чем в одном потоке, потому просачивание объекта из одного потока в другой вызывает катастрофические последствия.

Можно замутить атомарные операции на питоне, которые будет писать сам пользователь для описания превращений сложных структур, но есть проблема с побочными эффектами и гарантией потокобезопасности, потому что:
- любая функция в питоне использует все вышестоящие контексты, а также имеются замыкания, которые могут использовать свои собственные контексты;
- в CPython довольно сложно понять, работает ли функция с побочными эффектами или без онны. Речь не только про код на питоне, но еще и про код на си.

Для решения проблемы со стандартными бибиотеками и сборщиком мусора сообразительные люди довольно быстро придумали Jython, позже - IronPython, а потом и PyPy. Все три реализации привязывают вас к соответствующей среде выполнения, и это неприкольно даже несмотря на возможность выйти из них в расширения на C или других языках - проще написать уже всё на крестах или жабе, чем морочиться с пирамидами языков.

В итоге, на текущий момент, многопоточная разработка сводится к настройке внешних по отношению к питону потоков, как то сделано в NumPy, а потому разработать отзывчивый GUI на одном питоне не получится, например.

Кто-то видит свет в конце тонеля? Или же это предсмертные видения?

реализацией независимых интерпретаторов в одном процессе можно будет пользоваться

Нельзя этим пользоваться, ибо говно неюзабельное. Между сабинтепретаторами по каналам всего пару типов можно гонять.

каналы как средство взаимодействия потоков убоги даже в Go

Глупость-то какая.

WitcherGeralt ★★ ()

перевод питона на поддержку независимых интерпретаторов в одном процессе.

Нужность не доказана. Обоснование пепа — ну давайте мы палочкой потыкаем, может из этого кода-нибудь что-то годное выйдет.

а потому разработать отзывчивый GUI на одном питоне не получится

Для отзывчегого гуя многопоточность нафиг не нужна. Нужно в реальном времени обрабатывать события.

anonymous ()
Ответ на: комментарий от WitcherGeralt

Нельзя этим пользоваться, ибо говно неюзабельное. Между сабинтепретаторами по каналам всего пару типов можно гонять.

Нет необходимости гонять что-то кроме байт. multiprocessing живёт же.

anonymous ()

Для людей, которые уже готовы отвечать «ну дык есть же многопроцессы, есть форк на никсах - вот и используйте их», напомню, что реализация форка процесса на уровне ОС-и в действительности мало помогает многоПРОЦЕССовости питона - машем ручкой сборщику мусора, который передергивает счетчики ссылок:

И чо? Было у тебя по 100мб разных объектов на каждый процесс, теперь будет по 100мб разных объектов на каждый интерпретатор внутри одного процесса.

anonymous ()
Ответ на: комментарий от WitcherGeralt

Больше сериализаций и десериализайий богу оверхеда, а то питон невмоготу быстрый.

А ты ждёшь перемен?

Написано же, будут решать, когда оно заработает. Медленнее не стало и ладно.

anonymous ()

- каналы как средство взаимодействия потоков убоги даже в Go;

не знаю, что в них убогого, но в Go каналы не являются средством взаимодействия потоков.

В Python может быть когда нибудь уберут GIL. Чего обсуждать то, пока не убрали. Автор языка сказал, что в принципе не против его упразденения, но не знает способа это сделать без ухудшения производительности в однопоточном режиме. Нестандартными Python-ами пусть пользуются нестандартные программисты.

Partisan ★★ ()
Ответ на: комментарий от anonymous

От CPython я давно ничего хрошего не жду. Убогая асинхронна модель уж совсем не ложится даже на нерабочие потоки, на это никогда не ляжет и подавно, так что процессы и только процессы. Не вижу смысла городить какую-то дичь с сабинтерпретаторами, когда предусмотреть масштабирование на уровне архитетуры банально проще.

WitcherGeralt ★★ ()
Ответ на: комментарий от Partisan

в Go каналы не являются средством взаимодействия потоков

Че же они в таком случае являются?

не знает способа это сделать без ухудшения производительности в однопоточном режиме

PyPy STM был быстрее CPython (но медленее PyPY). Ну, и было бы что терять, производительностью в любом случае не пахнет.

WitcherGeralt ★★ ()
Ответ на: комментарий от ggrn

А зачем тебе гуевину на питоне писать, когда есть жабаскрипт? Веб приложений хватит всем.

Такого вырвиглазного неюзабельного УГ, как в Atom? Спасибо, не надо.

seiken ★★★★★ ()
Ответ на: комментарий от eternal_sorrow

и не нужно, есть биндинги к тулкитам на C

Может я отстал от жизни, но какой такой биндинг позволит выполнить непрерывные вычисления на питоне секунд так десять, периодически читая и изменяя интерфейс, но не вешая его? Да, ввод-ввывод не вешает программу, если он сделан отдельным питоновым потоком, потому что ввод-вывод останавливает поток и освобождает GIL, но ввод-вывод является одной из многочисленных возможных операций.

byko3y ()
Ответ на: комментарий от WitcherGeralt

Между сабинтепретаторами по каналам всего пару типов можно гонять

Через pickle/marshal можно довольно много чего перекидывать, как я уже написал. Другое дело, что multiprocessing это тоже умеет, но это весьма неудобный механизм, который в итоге сводится к передаче примитивных структур и их иерархии.

каналы как средство взаимодействия потоков убоги даже в Go

Глупость-то какая.

https://gist.github.com/kachayev/21e7fe149bc5ae0bd878
Каналы являются очень низкоуровневым механизмом, и для реализации даже весьма простых задач, чуть сложнее чем «передать одно значение в одну сторону», приходится городить систему каналов, в которой обязательно будут баги.

byko3y ()
Ответ на: комментарий от anonymous

Для отзывчегого гуя многопоточность нафиг не нужна. Нужно в реальном времени обрабатывать события.

Питон довольно своеобразно интерпретирует обработку в реальном времени, он умеет ждать и думать о вечном.

byko3y ()
Ответ на: комментарий от WitcherGeralt

Больше сериализаций и десериализайий богу оверхеда, а то питон невмоготу быстрый.

По-моему, медленность питона как раз намекает на допустимость некоего оверхеда. Другое дело, что возможностей сериализация-десериализация дает мало, памяти жрет много, а реализация сложных сценариев будет вести к багам, которые недавно всплывали даже в фундаментальных классах multiprocessing-а.

byko3y ()
Ответ на: комментарий от Partisan

не знаю, что в них убогого, но в Go каналы не являются средством взаимодействия потоков.

А что является? Конечно, я имел в виду безопасное взаимодействие - никто не запрещает выстрелить себе в ногу, использовав общую переменную без синхронизации.

В Python может быть когда нибудь уберут GIL. Чего обсуждать то, пока не убрали. Автор языка сказал, что в принципе не против его упразденения, но не знает способа это сделать без ухудшения производительности в однопоточном режиме

Это то, от чего плюются оседлавшие жабу люди давным-давно: прочитали страницу - помыли руки - перелистнули страницу - прочитали страницу... Потеря производительности на мелкогранулированых блокировках серьезная даже для такого тормоза, как питон. В том числе тормозом нужно делать сборщик мусора - и получается прям вообще беда. Потому в ближайшие лет десять в CPython GIL никто не уберет. Собсна, в PyPy GIL тоже не убрали, хотя есть потенциал и некоторые рабочие попытки, но при этом еще и все обычные расширения на C-интерфейсах автоматически отваливаются.

byko3y ()
Ответ на: комментарий от WitcherGeralt

Убогая асинхронна модель уж совсем не ложится даже на нерабочие потоки, на это никогда не ляжет и подавно, так что процессы и только процессы

У multiprocessing есть много проблем, как то упомянутые мной контексты, которые в нынешней реализации просто копируются в дочерних процессах с надеждой, что никто их не изменит. Процессы в данном случае являются лишь сортами говна - я не понимаю, почему какому-то сорту нужно отдать предпочтение. На текущий момент просто нет выбора, потому приходится есть что дают.

byko3y ()
Ответ на: комментарий от byko3y

Я и говорю, всё это вообще не нужно. Дробишь приложение и распихиваешь в контейнеры, пишешь вспомогательные прокси, чтобы дробить то, что на первый взгляд дробить нельзя. А там уже за счёт масштабиования оверхед сто раз окупается.

WitcherGeralt ★★ ()
Ответ на: комментарий от WitcherGeralt

PyPy STM был быстрее CPython (но медленее PyPY). Ну, и было бы что терять, производительностью в любом случае не пахнет.

Нужно всегда давать контекст, ведь мы оба понимаем, как работает PyPy, за счет чего получает прирост производительности, и за счет чего в некоторых задачах он работает медленнее CPython.
https://morepypy.blogspot.com/2014/11/tornado-without-gil-on-pypy-stm.html
Здесь PyPy дает серьезное преимущество, потому что числодробилки и проход по графу. При этом STM просаживает производительногсть в полтора раза, а на 4 ядрах STM дает производительность одноядерного STM, помноженного на 3,5 - уже здесь видна потеря, которая, естественно, будет расти с каждым следующим ядром. А ведь на всех домашних пеках уже минимум 4 потока.

byko3y ()
Ответ на: комментарий от ggrn

А зачем тебе гуевину на питоне писать, когда есть жабаскрипт? Веб приложений хватит всем

Сорта говна. На питоне тоже можно было бы сделать тулкит, который бы работал с фейсом независимо от главного потока, но это никому не нужно, потому что требует переучиваться на новые механизмы взаимодействия. В хромоприложениях есть обратная проблема - как от странички в браузере вернуться к родственности ОС-и.

byko3y ()
Ответ на: комментарий от WitcherGeralt

Я и говорю, всё это вообще не нужно. Дробишь приложение и распихиваешь в контейнеры, пишешь вспомогательные прокси, чтобы дробить то, что на первый взгляд дробить нельзя. А там уже за счёт масштабиования оверхед сто раз окупается.

Я думаю, ты понимаешь, что такая система начинает иметь смысл примерно с десятка рабочих процессов и пары компов. Распределенные системы всегда тяжелее разрабатывать - а может мне нужно просто на единственном сервере обрабатывать запросы на всех ядрах? Масштабируемость ограничена, контейнеры оказываются лишней сущностью. Тут не все бигдата сидят.

byko3y ()
Ответ на: комментарий от Ford_Focus

Щито есть скриптовые языки? Некоторые Erlang относят к скриптовым. Он, кстати, ближе всего к этой самой модели «несколько интерпретаторов в одном процессе». Причем, эти независимые потоки в эрланге зовутся процессами.

byko3y ()
Ответ на: комментарий от WitcherGeralt

AMQP, например.

Блин, ну это уже настолько тяжелая артиллерия... У нас производительность одного потока питона неудовлетворительна, так давайте сделаем распределенную систему с менеджером очереди и ботнетом из контейнеров. При таких раскладах можно думать о том, как заменить питон более адекватным инструментом, забыв про AMQP и распределенность.

byko3y ()

а потому разработать отзывчивый GUI на одном питоне

Шта????
А, выше написали, что не нужно, а нужно биндить к отзывчивым тулкитам.

Shadow ★★★★★ ()
Ответ на: комментарий от anonymous

Для отзывчегого гуя многопоточность нафиг не нужна. Нужно в реальном времени обрабатывать события.

Да ладно, аффтор явно имел ввиду суровое приложение в одном потоке, когда ГУЙ лочится, пока что-то там считается. А когда гуй кидают в отдельный поток, он ещё и мигать и показывать может.

Shadow ★★★★★ ()
Ответ на: комментарий от WitcherGeralt

Убогая асинхронна модель уж совсем не ложится даже на нерабочие потоки

Мне кажется, это чтобы говнобыстрокодить, а не за всё хорошее против всего плохого.

Shadow ★★★★★ ()
Ответ на: комментарий от byko3y

Не благодари:
https://wiki.wxpython.org/Non-Blocking Gui
- проверял, работает, UI не лагает.
Ты же в курсе, что треды питона выполняются параллельно внутри процесса и обычно не блокируют его?

Shadow ★★★★★ ()
Последнее исправление: Shadow (всего исправлений: 1)
Ответ на: комментарий от byko3y

Ты сильно переоценивашь оверхед. Мне даже захотелось потестить. Какие бы данные погонять так, чтобы и по io жирно было и чтобы потом что-то посчитать / преобразовать уже тяжко по процу?

WitcherGeralt ★★ ()
Ответ на: комментарий от Shadow

А, выше написали, что не нужно, а нужно биндить к отзывчивым тулкитам.

А я уже выше написал вопрос на ответ - к каким тулкитам биндить? Tkinter?

Не благодари:
https://wiki.wxpython.org/Non-Blocking Gui
- проверял, работает, UI не лагает.
Ты же в курсе, что треды питона выполняются параллельно внутри процесса и обычно не блокируют его?

Я аналогичную халтуру часто делал в единственном потоке, запихивая этапы длинной операции в очередь главного цикла. Пример с wx просто использует, так сказать, «главный цикл интерпретатора» вместо цикла GUI.
Этот подход, на самом деле, дает плохие результаты, которых мы не увидели в примере с wx потому, что в потоке нет абсолютно никакого взаимодействия с интерфейсом - с таким же успехом можно было бы запустить отдельный процесс, и тогда вся логика работала бы в нем, а главный процесс занимался бы исключительно GUI.
Треды ОС выполняются параллельно, но сами операции питона никогда не выполняются параллельно, а только поочередно. Если какая-то операция внезапно заняла много времени - все потоки питона зависают. В связи с этим недавно переделывали алгоритм приоритизации взятия GIL, поскольку отдельные потоки раньше могли брать себе сильно больше времени процессора, чем остальные.

byko3y ()
Ответ на: комментарий от Shadow

Убогая асинхронна модель уж совсем не ложится даже на нерабочие потоки

Мне кажется, это чтобы говнобыстрокодить, а не за всё хорошее против всего плохого.

Речь про async/wait? Он есть и в дотнетах, и в JS, и он там исполняет роль синтаксического сахара обратных вызовов, а никак не параллелизует что-то там.

byko3y ()
Ответ на: комментарий от WitcherGeralt

Ты сильно переоценивашь оверхед. Мне даже захотелось потестить. Какие бы данные погонять так, чтобы и по io жирно было и чтобы потом что-то посчитать / преобразовать уже тяжко по процу?

Я прекрасно оцениваю оверхед, но ты не понял, в каком месте он заключается. Питон нужен для быстрой разработки каких-то набросков, часто меняющейся логики, или просто кучки говна, надежность и производительность которой не имеет значения для заказчика.
Если же ты разрабатываешь распределенное приложение с координатором - ты уже запарился, ты уже допустил перерасход человеческих ресурсов, так называемое technical debt. Эти человекочасы можно было направить на переписывание логики в более быструю форму, которая позволила бы забыть про распределенность, что в перспективе дало бы продукт, более производительный по отношению к ресурсам компа, и более удобный в поддержке, потому что вот он сам и ему больше ничего не нужно.

byko3y ()
Ответ на: комментарий от byko3y

в потоке нет абсолютно никакого взаимодействия с интерфейсом

Там же система сигналов и очередь!

запустить отдельный процесс, и тогда вся логика работала бы в нем, а главный процесс занимался бы исключительно GUI

И... Что в этом случае плохого, если больше ничего другого нет?

Shadow ★★★★★ ()
Ответ на: комментарий от byko3y

Треды ОС выполняются параллельно, но сами операции питона никогда не выполняются параллельно, а только поочередно.

Дык, а если у нас однопоточное одно ядро? Тогда и треды ОС такие же.

Shadow ★★★★★ ()
Ответ на: комментарий от Shadow

Там же система сигналов и очередь!

Да, отсылается событие окончания потока. Но вся работа потока не связана с интерфейсом. Если бы была необходимость несколько раз в секунду производить взаимодействие питон-интерфейс, то выяснилось бы, что отзывчивость интерфейса как минимум сильно снизилась. Вы можете удостовериться в этом, если в примере https://wiki.wxpython.org/Non-Blocking%20Gui замените sleep на любое долгое вычисление и попробуете быстро нажимать на кнопку - интерфейс провисает, и происходит это именно из-за взаимодействия GUI-питон.
Пока GUI и питон не взаимодействуют - всё прекрасно: гуй отрисовывается, питон молотит свои команды. Но смысла в этом приложении довольно мало.

запустить отдельный процесс, и тогда вся логика работала бы в нем, а главный процесс занимался бы исключительно GUI

И... Что в этом случае плохого, если больше ничего другого нет?

Трудоемкость написания кода. Два процесса взаимодействуют друг с другом через маленькую щёлку. Хоть multiprocessing и позволяет построить более широкую абстракцию из этого канала, но всё равно остается ощущение, будто пытаешься перемещаться с ногами в мешке.

byko3y ()
Ответ на: комментарий от Shadow

Дык, а если у нас однопоточное одно ядро? Тогда и треды ОС такие же.

В реальности у нас большинство устройств имеют большое число ядер. Тред так-то в 2019 году был создан.

byko3y ()
Ответ на: комментарий от WitcherGeralt

Некорректно обобщаешь.

Так напиши, в чем некорректность. Мне сложно представить ситуацию, когда переход на распределенную архитектуру дал бы меньше издержек, чем переписывание на «быстром» языке. Разве что если есть много индусов, которые готовы с утра до вечера за миску риса биться головой об клаву - тогда их не жалко, пусть сколько угодно работают.

byko3y ()
Ответ на: комментарий от aploskov

Починят ценой сильной поломки совместимости и будет у нас Python 4 vs Python 2. Нормально всё будет. Будем жрать кактус и радоваться жизни

Что починят? Тебя не смущает, что в PEP стоит версия питона 3.9?

byko3y ()
Ответ на: комментарий от WitcherGeralt

Блин, да почему я должен из тебя каждое слово тащить?
В 2019 компьютеры стали по другому принципу работать? Питон или жаба стали каким-то другим? Разработка распределенных систем всегда была дороже и геморнее, потому что их сложно отлаживать, потому что писать взаимодействовать между процессами и компьютерами тяжелее, чем работать в одном процессе. Горсть независимых служб требует настройки и обслуживания поверх требуемой для настройки единственной службы, а тебе еще и менеджер очереди нужно настраивать. Итого плёвая служба превращается в цирк с клоунами и канатоходцами.
Сказки про «не несёт особых издержек» сочиняют те, кто не хочет рассказывать про альтернативы, по сравнению с которыми издержки становятся хорошо видны.

byko3y ()