Как стать автором
Обновить

Комментарии 58

Простите за упрямство, но чем все-таки Stackless и Concurrence принципиально лучше встроенных Thread, ThreadPool, Queue для синхронизации и т.п.?

Я вижу, что например Stackless имеет очень приятные фичи — kill для потока и сериализацию потока. Да, это круто. Но все же, с точки зрения именно параллельного программирования — чем оно принципиально лучше?
Принципиально ничем, только производительность выше. Для примера посмотрите на функцию get(), которую я привёл в посте. В многопоточной программе, чтобы избежать race condition, нужно было бы завернуть тело функции в мьютекс. В Stackless оно не надо, ибо многозадачность кооперативная.
> Принципиально ничем, только производительность выше.
А вы точно сами понимаете, что написали? Именно принципиально они и отличаются.
ну например тем, что изза GIL писать многопоточные программы в python вообще не стоит, исключительно асинхронные вещи и IPC.
Хорошо, согласен. Но корпоративная многозадачность подразумевает явную передачу управления другому потоку. Как в таком случае реализовать, например, параллельную закачку файлов? Ведь библиотечный urlopen не имеет внутри закладок для передачи управления. Будет ли это работать?
urlopen не содержит закладок, да. Но вы пользуйтесь не urlopen, а HTTP-клиентом из Concurrence:

conn = HTTPConnection()
сonn.connect(("hostname", 80))
try:
    req = conn.post("/path/name", data)
    req.add_header("Content-type", "multipart/form-data")
    req.add_header("Content-length", len(data))
    res = conn.perform(req)
finally:
    conn.close()

Всё будет отлично работать впараллель.
Но, к сожалению, оно не обладает тем огромным функционалом, который предоставляет стандартная библиотека.
gevent умеет патчить стандарные функции для работы с сокетами, делаях их асинхронными — после этого стандарные либы автоматически начинают работать асинхронно тоже.
упс, сначала читать все, потом писать — все время забываю )
там urlopen заменен на асинхронную версию, т.е. когда вы делаете urlopen — управление передается интерпретатору.
в stackless для этого все написано несколько версий socket библиотек (e.g. stacklesssocket)

например что-то вроде:

import stacklesssocket
stacklesssocket.install()

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

И второй вопрос: а нету ли оберток над этим делом, симулирующих стандартные интерфейсы Thread и ThreadPool? Честно скажу, с GIL-ом было много проблем, и я не против попробовать эту библиотеку, но переписывать на нее весь код — будет мягко говоря накладно. Притом, что я не вижу никаких особых препятствий сделать такую обертку.
стандартные операции с питоновскими сокетами — станут асинхронными, написаные без их участия — нет.

я думаю вам стоит посмотреть на twisted. это, наверное, одна из лучших вещей, которую я видел в питоне. во-первых — это стандартный cpython, т.е. можно быть уверенным в том, что не придется пилить чужие исходники. во-вторых — там есть фактически все тоже самое, заменяем каналы на DeferredQueue и получаем сходный эффект. Надо, конечно stateless функции писать, но это как и везде.

ну и обычные методы для IPC — amqp. или можно глянуть zeromq, если хочется совсем скорости
т.е. манкипатчинг стандартного сокета возможен… Это хорошо… А как быть например с запросами к MySQL через MySQLdb? Они будут блокировать интерпретатор глобально или во время запроса к БД смогут параллельно выполняться другие тасклеты?
Протокол MySQL полностью реализован на чистом пайтоне в Concurrence как раз для того, чтобы сделать его неблокирующимся.
Плохо, не согласен. Смысл есть. Просто он не в том, чтобы загрузить все ядра и получить бОльшую производительность.
Впрочем, ты сам сказал: «исключительно асинхронные вещи»… В теперь объясни, для чего ещё использовать несколько потоков в программе, как не для асинхронных вещей?
я так понимаю это коммент мне =)
асинхронное программирование, лично для меня, дает некоторые плюсы — отсутствие синхронизации. это очень здорово упрощает программирование, т.к. я здорово гробил силы на многопоточно-специфические вещи.
для чего стоит делать много потоков — именно загрузить много ядер, ведь если одно ядро справляется, а все остальное ввод-вывод, то смысл блокироваться на этих событиях? я понимаю, что проще использовать много потоков, что бы сделать то, что можно сделать асинхронно, но это только если ты привык писать многопоточные вещи.
Твой ответ запутал меня…
У меня достаточно опыта написания многопоточных программ (питон и Си) и всегда я использовал многопоточность для выполнения неких асинхронных действий. В общем-то ловил двух зайцев: хорошая, красивая архитектура приложения и отсутствие блокировки на IO.
И GIL мне совершенно не мешал. Если перегруз, то делаем распределитель и раскидываем действия на два-три системных потока и не паримся, так как затраты на их использования невелики (пока их два-три, конечно). Есть там, правда, пара подводных камней, но всё обходится и решается.
Может тем, что это не треды? Прочтите внимательнее, прочтите другие статьи, если эта не ясна.
И главное — тасклеты это coroutines, сопрограммы, но никак не треды/потоки!
Я не спрашивал, чем оно отличается. Я спрашивал, чем оно лучше. Это совершенно разные вопросы: мне нет смысла менять одну технологию на другую просто потому, что она другая (более продвинутая, более новая или еще какая-то более клевая), но при этом не дает ощутимой выгоды.

Поэтому прежде чем что-то делать, я хочу понять, а надо ли мне вообще это делать.
> очень приятные фичи — kill для потока
Когда я вижу такое, мне хочется выть… Убивать поток/процесс до его нормального завершения нельзя никогда. То есть вообще.
Нужно заставить поток завершиться самому. Корректно и без лишних воплей. Если у вас это не получается, значит вы где-то напортачили с архитектурой всего своего многопоточного хозяйства и делаете что-то не то.

Единственное исключение, это kill -9, но это немного из другой оперы.
> Когда я вижу такое, мне хочется выть… Убивать поток/процесс до его нормального завершения нельзя никогда. То есть вообще.

Не стоит быть настолько категоричным.

В потоке может выполняться совершенно различный код, в том числе тот, который вы сам не писали: это может быть API-функция, метод из сторонней библиотеки, реализация которого вас не интересует и т.п. А вам просто нужно, чтобы этот код выполнялся не более чем конечное время (возможно заранее заданное), в таком случае в «убийстве» потока нет ничего предрассудительного.
Ещё немного порекламирую Concurrence:

try:
    with Timeout.push(10):
        do_something_heavy()
except TimeoutError:
   do_some_recovery()

А API-функцию в асинхронной программе вы не прервёте. Пока она выполняется, другой код всё равно управление не получит.
Нет, возмущаться про убийство нитей можно долго… Но это красивый код. :-)
> А API-функцию в асинхронной программе вы не прервёте. Пока она выполняется, другой код всё равно управление не получит.

Я не знаю как в стеклесс, но в обычном питоне WinAPI функция CopyFile у меня прервалась при убийстве нити методом _Thread__stop().
В Stackless нет. Прерваться может только на моменте, когда тасклет добровольно отдаёт управление планировщику. Точнее, есть ещё режим вытесняющей многозадачности, когда можно по счётчику инструкций отбирать управление у тасклета, но к внешним по отношению к Python функциям это не относится.
Хм… Ну может быть. С таким не сталкивался.
Вообще, это всё равно плохо, так как провоцирует писать плохой код. Я вот, когда был молодым да зелёным, очень матерился на то, что нельзя просто убить нить. Qt'шную можно, а обычную нельзя! Это было очень неприятно и просто бесило. Потом, напарившись с неделю с одним странным глюком, я понял, что прибивать qt'шную нить нельзя в принципе. Никогда и ни за что. Лучше ей флажком со всей дури дать и подождать, пока она сама сдохнет.
Есть разные системы с разными подходами. Например, в OS Inferno неблокирующего I/O нет в принципе, любой read/write блокирующий. И единственный способ реализовать те же таймауты на read/write — убить нить. Так задумано архитектурно, и никогда из-за этого никаких «странных глюков» не возникает.
Проблема с убийством нитей только в том, что в этот момент они могут находиться в весьма пикантных состояниях, например внутри malloc. Убил — нарушил работу аллокатора для всего процесса. Я не знаю, как вопрос решен в Inferno, но либо должны существовать какие-то меры против этого, либо безопасно убивать нити не получится.
В Inferno используется VM, и на Limbo нет прямой работы с памятью. Поэтому все malloc-и делаются VM, равно как и kill нити делается тоже VM. Так что у VM есть всё необходимое для того, чтобы убивать нить только когда это безопасно. Но программист ничего этого не видит, он просто может в любой момент безопасно убить любую нить.
и ни слова про epoll?
libevent, на котором построен Concurrence, умеет epoll.
Мне gevent больше импонирует, проще и быстрее.
Его хотели портировать под Stackess в рамках Google Summer of Code, но не портировали. Почему — не знаю, кстати. То ли заявку не приняли, то ли не сделал никто.
Там один разработчик :) Денис Биленко
Сейчас разбирается вопрос об инвестициях, но не знаю соглашается он или нет.
Понимаю, что вопрос скорей всего в пустоту (вряд ли этот пост будет просматривать человек который является специалистом и в java и в python), но всё же: за счёт чего stackless python может выиграть в производительности у sun/oracle JVM ну и я зыка JAVA?
Событийная работа с сокетами? есть NIO + библиотеки поверх них типа netty/MINA
Тасклеты и однопоточный планировщик, который который переключает тасклеты при блокировки? Ну так и в JVM — зеленые потоки и планировщик. нить заблокирована — управление передано другой нити. Так что на однопроцессорной однаядернйо машине — выполняется только один поток.
на многоядерной — можно привязать к определённому ядру. Чтобы ибежать кеша потока на общих данных использовать volitile.
Где выигрыш?
Выигрыш чего перед чем? Пост про Python, в общем-то. Если Java умеет поставить зелёный поток в слип на ожидании ввода-вывода, а когда надо разбудить, то это же здорово. Это как раз и есть та же технология, про которую я писал.
Тут речь не о выигрыше в производительности, а в простоте разработки. Нету потоков — нету факапов с race conditions. Плюс сам по себе пайтон раз в десять-двадцать по скорости разработки фору джаве даст. На том и выезжают.
Я бы уточнил, что производительность все таки вырастает, засчет того, что поток исполнения не блокируется на ожидание IO. Другое дело, что coroutines не прерогатива python, и впервые были вообще не в python реализованы.

По производительности на тестах с CLBG java сильно обгоняет python как числодробилка, вот только не всегда и не везде нам надо много считать, на некоторых задачах java не так уж сильно обходит python.

Ну и потом — производителность java это производительность ее VM. Для python параллельно разрабатываются два jit, так что недолго ему осталось ходить в тормозах.
Что не нравится всегда можно в pyrex, cython :) или native C :D
А какой второй кстати? Кроме pypy?
unladen swallow
Так он же вроде сдулся? В смысле его допилили, он не дал такого буста как ожидалось, гугловцы написали что это потому что питон сам по себе такой медленный и ничего не поделать, и теперь его просто вольют в питон и все — никакой революции. Разве нет?
Нет, не сдулся. Js ничем не лучше, а буста ему те же гугловцы вдули от и до.
Ну я в общем вот про это говорил
«I don't think it's possible to make an implementation like CPython as
fast as an engine like V8 that was designed to
be fast above all else. We've come up with some optimizations already
that would simply be too difficult to implement in CPython, and so we
had to discard them. Being a volunteer-run open-source project,
CPython requires somewhat different priorities than V8: CPython places
a heavy emphasis on simplicity, the idea being that a simple, slower
core will be easier for people to maintain in their free time than a
more complicated, faster core. I have high hopes for one of the other Python implementations to
provide a longer-term performance solution designed without the
shackles of C-level backwards compatibility. „
Ах это, да читал, но подзабыл. Тогда в основном pypy получается. В общем-то для python в принципе желательны варианты — кому то менее прогрессивный, но более навороченный вариант, кому то CPython.
Интересно почему в Stackless не реализовали автоматическое переключение между тасклетами при блокирующих IO операциях на уровне сокетов/файлов?

Это в принципе нереально организовать или слишком затратно?

//Видимо не зря я за Erlang взялся…
В принципе, так делают. Манкипатчат вызовы функций socket и подставляют вместо них «асинхронные» версии. Теоретически ничего этому не мешает, но практически нормальной реализации нет, которая различает блокирующий и неблокирующий режимы, умеет понимать разницу между read(buf, 1024) — «прочитай 1024 или можешь не возвращаться» и «прочитай хоть сколько-нибудь, но не больше 1024».

Что касается Erlang, то это отличный язык, от рождения сделанный асинхронным. Единственная проблема — его применение в коммерческом проекте. Где найти знающих его программистов? С Python попроще будет.
есть мнение что его можно выучить =)
Erlang немного не о том. В стаклес у вас вообще одна нить выполнения, только одна, а Erlang умеет задействовать все процессоры, потому что у него не завязано все на одну нить. Ну и вообще не так все просто, как вы себе представили — в Erlang типа нельзя поменять значение «переменной», а потому гонки там и невозможны, а вовсе не из-за того, что нитей нет тупо.
> Erlang типа нельзя поменять значение «переменной»
Впрочем, это не мешает разработке :)
Там просто по-другому подходишь к задаче: функция разбивается на маленькие локальные единицы смысла, в пределах которых нет необходимости менять значение переменной.
Александр, это офигенно!
Хочу попробовать, как раз в отпуске может найду часок :)
забавно наблюдать в очередной как изобретают erlang
Ох…
Вот у меня тоже главные языки Python и Erlang. И не все так однозначно. У питона есть мощное преимущество в наличии огромного количества библиотек, нормальная поддержка для тех же юникодных строк, и другие инструменты, которые облегчают базовые рутинные операции. В эрланге же намного сильнее основа: и по скорости работы, и по надежности, и даже есть возможность безопасно kill`нуть процесс.
А насколько он вообще готов к продакшну? Скорость, стабильность, поддержка имеющихся библиотек питона (в т.ч. C-шных), как тот же джанго работает на stackless? Особенно интересует preemptive — не хочется думать о явной передаче управления другим нитям, тем более что и код в уже существующих библиотеках вряд ли об этом думает.
На самом stackless работает онлайн-игра Eve Online. Самый что ни на есть ацкий продакшн, причём с колоссальными нагрузками. Что касается джанго, ничего сказать не могу — не пробовал. Но принципиально не вижу никаких проблем — должно завестись с пол-оборота.

Preemptive убивает всю полезность stackless. Вы уже не можете быть уверены, что ваши операции атомарны. О передаче управления задумываться не надо вообще, поскольку оно происходит само на любом вводе-выводе. Если речь идёт о web-приложениях, то там этот ввод-вывод постоянно идёт в запросах к кэшам и базам данных. Ничего специально тюнить не надо.

Проблемы с существующими библиотеками могут быть только в одном месте — если они сами делают какой-то ввод-вывод, который может заблокировать процесс. Пока он не будет отдавать управление, остальные микропотоки (которые могли бы выполняться) будут тоже стоять и ждать. Решение — адаптировать каждую используемую библиотеку для stackless — заворачивать сетевой ввод-вывод в сокеты concurrence. Для многих библиотек это уже сделано, для некоторых — ещё нет. В частности, нет реализации SSL-сокетов. Поэтому смотрите по задаче — если используете экзотику, надо её дорабатывать. Если не используете — тогда всё будет работать из коробки.
Preemptive убивает всю полезность stackless. Вы уже не можете быть уверены, что ваши операции атомарны.
А зачем мне атомарность операций? Просто не надо работать из разных нитей с одними и теми же структурами данных, всё общение между нитями только через каналы. Проблема в том, что я могу гарантировать соблюдение этого для своего кода, но не для используемых библиотек.

В веб-приложениях тоже иногда встречаются тяжёлые запросы в базу, а база это далеко не всегда MySQL — есть ещё NoSQL. Например, данные могут храниться в обычных .json файлах, и тогда этот «тяжёлый запрос в базу» будет выполняться как чтение/запись множества .json-файлов с дополнительными вычислениями. Блокирующие чтение/запись обычных файлов тоже будет манкипатчиться и обрабатываться через epoll?

Кроме того, конкретно у нас встречаются тяжёлые вычислительно задачи, когда требуется просчитать все возможные сочетания кучи элементов — без preemptive очень сложно гарантировать что в других нитях не начнутся проблемы из-за слишком долгого простоя (например, при том же неблокирующем I/O, удалённая сторона может разорвать соединение из-за того, что наша нить не вычитывает данные из буфера ядра).

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

Получается, что пока речь идёт о том, чтобы писать неблокирующий I/O в простом блокирующем стиле на нитях — stackless полностью удовлетворяет наши потребности. Но когда речь заходит о general программировании на нитях, точнее о его простом CSP-шном варианте (а-ля Limbo или Go), когда на каждый чих делается новая лёгкая нить и всё общение между ними делается через каналы — здесь возникает необходимость в preemptive, и, судя по всему, несовместимость с большинством библиотек. Возможно я ошибаюсь, и буду рад, если Вы мне на это укажете, но пока впечатление складывается именно такое.

Что касается Eve Online — я подозреваю, что там stackless используется именно в том качестве, в котором он хорош — для упрощения неблокирующего I/O и без preemptive.
Атомарность полезна для исключительно быстрой реализации примитивов синхронизации. if busy == 0: busy = 1 [some-code] busy = 0. Если это не требуется, то конечно preemprive будет работать.

Про NoSQL вы всё правильно написали. Если обращение к базе по сети, то можно сокет подменить, и всё будет работать. Мы так работаем с Cassandra (она на Thrift). Не будет проблем с CouchDB (он на HTTP). Если надо читать стопицот файлов, то да — тут проблемы будут. Concurrence не умеет файлы через epoll диспетчерить. Это надо специально её этому учить.

Что касается библиотек, то если они внешние по отношению к Python, то в них счётчик инструкций вообще работать не будет, и шедулер не сможет выдернуть оттуда управление. Так что для таких задач даже Preemptive неприемлем.

В Eve Online stackless используется именно для неблокирующего I/O и без preemptive, насколько я знаю.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории