На днях Саймон Виллисон написал очень хороший пост про node.js. Саймону очень удаётся писать такие intro-посты про технологии, которые очень ясно передают самую суть вещей. Когда-то он так сподвиг меня взяться за изучение Джанго. Пост про node.js тоже хорошо даёт понять, почему это круто, и какое место в современной экосистеме веба он занимает. Кто ещё не слышал про node.js — начинайте читать с этого поста.
Но мой пост не об этом. Просто, одной из своих фраз Саймон напомнил мне о моей собственной мысли, которая давно не даёт мне покоя, но пока всё никак не оформлялась:
Node represents a clean slate. Twisted and EventMachine are hampered by the existence of a large number of blocking libraries for their respective languages.
Это, то почему я без восторга смотрю например на Tornado и на всякие задорные попытки запускать под ним ту же Джангу. Однако пост Саймона что-то вдруг повернул у меня в голове, и то, что раньше казалось мне проблемой, теперь кажется решением :-).
Впрочем, обо всём по порядку. (Осторожно, пост длинный!)
Мне известны всего два принципиально разных способа делать многозадачность.
Самый распространённый способ — исполнение кода в виде нескольких процессов или тредов. Неудивительно, что он появился самым первым и стал распространён: большую часть забот по распараллеливанию исполнения программы берёт на себя платформа — ОС или runtime языка. Программист же пишет код в знакомом с детства однопоточном синхронном стиле. Да, конечно есть проблемы с передачей данных между процессами, и проблемы с общими данным в тредах, но мы их как-то с грехом пополам научились решать. По крайней мере в не очень сложных случаях.
Особенно комфортно с этим подходом в веб-программировании, где специфика позволяет вообще не общаться процессам друг с другом иначе как через БД. А это просто. И именно поэтому любой PHP-программист в состоянии написать очень многозадачную программу.
Проблема этого подхода — в производительности. Её, конечно, хватает сейчас почти на всё. Но начиная с какого-то момента многопроцессная система начинает кушать просто очень много памяти. Потому что, крути ни крути, эту память приходится копировать в каждый изолированный процесс (и copy-on-write до конца не спасает). Переключение на треды решает эту проблему, но снова не до конца. С ростом количества тредов система упирается теперь уже в резко возрастающее время простоя на локах, без которых синхронно в тредах программировать невозможно. Многим наверное знакомо типичное поведение веб-систем, запущенных в тредном режиме под нагрузкой: в какой-то момент она внезапно начинает сыпать 500-ми ошибками.
А хочется-то большего. Хочется десятков тысяч одновременных клиентов и тысяч RPS.
С такими нагрузками справляются системы, написанные в асинхронном стиле. Как только мы отдаём IO на откуп ОС и не блокируем свой процесс, у нас высвобождается куча времени: ведь очень много веб-приложений ничего очень долгого и умного не делают, кроме того, что плюют небольшой 20-килобайтный кусочек HTML'а в сокет. И этого "довольно много" на самом деле настолько много, что мы в состоянии обработать тысячи клиентов одним процессом, просто бегая по ним в цикле. И поэтому тривиальные "hello-world'ы" на Tornado уделывают любой многопроцессный сервер.
Но счастья нет и здесь. Проблемы начинаются, когда приложению нет-нет, да и приходится делать что-то сколько-нибудь долгое. Например получить из БД довольно большой и неудобный кусок данных и перегруппировать его императивными средствами. Или ужать картинку. Или обратиться сходить по URL'у на чужой сервер. Или отослать email. Или распарсить большой XML.
Как только однопоточной программе, обслуживающей в цикле десять тысяч клиентов, приходится ради одного из них задержаться на, скажем, секундочку, эту секундочку ждут все остальные. Если таких "требовательных" набрался десяток — всё, ни один клиент не получит ответ раньше, чем через десять секунд. Решение не масштабируется на реальные задачи, как говорят у них в бизнесе.
Обойти это можно, только если любые долгие операции как-то так переписать, чтобы они не блокировали выполнение основной задачи. Причём, как именно это делать, в общем-то, понятно. Можно уводить их в отдельные процессы-обработчики, можно использовать асинхронный IO — всё это уже опробовано. Но штука в том, что тот код, которым мы пользуемся сейчас — наши любимые библиотеки, которые парсят нам XML и отсылают письма, уже написаны в синхронном стиле.
Причём нам тут в вебе ещё повезло, у нас вся архитектура строится вокруг модели коротеньких ответов, и именно поэтому нам до сих пор удавалось держать десятки и сотни RPS с помощью традиционной многопроцессности. А вот если мы посмотрим, например, в сторону десктопного софта, мы увидим, что там похожая проблема стоит ещё острее. Какое-то время назад хардварные ребята показали нам фигу и сказали, что им поднадоело издеваться над кремнием в поисках там ещё бо́льших гигагерц, и единственное, что они могут нам предложить теперь — это 2 (или 4, или 32) параллельных ядра, и если мы хотим, чтобы наши программы работали быстрее, нам придётся придумать, как их использовать.
Однако десктопный мир полон софта, который написан в абсолютно синхронном стиле под один процессор. И от того, что мы запустим, скажем, две копии каталогизатора фотографий, они не прочитают содержимое флешки с фотками в два раза быстрее (сходите проверьте, если не верите).
Все эти наблюдения приводят к одному интересному выводу.
Если мы хотим, чтобы компьютеры работали значительно быстрее, нам придётся всё переписать в асинхронном стиле.
И вот знаете, я теперь как-то думаю, что это не проблема, а как раз решение. Думаю я так, потому что кажется умные дядьки, у которых есть время и способности думать над такими вещами, нащупали то, как именно надо писать многозадачные программы, чтобы программисты при это оставались в здравом рассудке. Поэтому я, чёрт возьми, завидую следующим поколениями программистов. Потому что переписать что-нибудь с нуля мы все иной раз не прочь, но в этом случае у них будут на это реальные основания.
Вот вам прямо из головы задачка: асинхронный параллельный парсинг XML. Как мы сейчас парсим XML:
Жутко же неэффективно! Почему лексинг/парсинг уже полученного куска данных должен блокировать получение следующего куска? Почему бы лексинг разных кусков не делать в несколько потоков (map), которые потом объединять в единый парсинг (reduce), причём возможно тоже в несколько потоков? Зачем нам дерево, если в большинстве случаев пользователь всё равно будет на основе дерева составлять свои структуры — давайте придумаем удобную обработку элементов в SAX-стиле.
Всё это не так просто, но кажется очень интересным.
Я думаю, что большое значение имеет то, какой именно язык выбрать для глобального переписывания всего. Те объектные императивные языки, которыми мы с успехом пользуемся сейчас, кажется, не смогут выжить в новом мире, так же, как, например, C++ не стал мейнстримным языком веб-программирования, когда оно вылезло из коротких штанишек. Да, на Плюсах можно писать веб, никто не спорит. Но это просто неэффективно.
Мой любимый Питон по определению теряет здесь одно из своих больших преимуществ — большое количество написанных библиотек. Как раз потому, что именно их и надо все переписать. Зато его не сильно заметные сейчас недостатки — GIL и отсутствие полноценных анонимных блоков кода — магическим образом вырастают в размерах и вполне могут оказаться решающими.
Так чем тогда пользоваться? У меня в мыслях есть несколько кандидатов, каждый из которых имеет шансы стать Главным Языком Десятилетия. Какой выиграет, сказать не возьмусь, просто потому, что это зависит не только от качеств самих языков, но и просто от того, кому больше повезёт с хайпом, поддержкой и логотипом.
Пользуясь случаем, направляю вас заодно на цикл статей Concur.next, где Тим Брей обсуждает ту же языковую проблему существенно подробней. Собственно, я с ним согласен чуть менее, чем полностью, поэтому мысли мои здесь не оригинальны.
Как многие говорят, функциональная природа языка — почти обязательное условие хорошей распараллеливаемости. Как только у вас есть неменяющиеся данные, вы, теоретически, избавляетесь от проблем с традиционным программированием с локами. Опять же теоретически, имея ленивые функции без сторонних эффектов, компилятор может в большой степени сам принимать решения о том, что и как можно параллелить. Я, впрочем, не большой знаток этого аспекта Haskell'а (да и остальных тоже :-)), поэтому могу тут здорово ошибаться.
В минусы языку записывается его большая сложность. Которая, в общем-то, убивает почти вся перспективу: если языком не можем пользоваться мы — туповатые инженеры — то писать и развивать библиотеки для реального мира становится некому.
Как и Haskell, Erlang — язык функциональный. Но главное, там всё программирование происходит в терминах создания асинхронных процессов, и поэтому писать на нём асинхронные программы получается естественно.
Язык, однако, архаичен и странен. Кроме того, маленькое коммьюнити тоже не дают думать о том, что он когда-то прочно войдёт в мейнстрим. Впрочем, совсем помереть ему тоже не дадут, хотя бы потому, что CouchDB уже нужна всем, а написана как раз на Erlang.
Apple придумала расширение к Си в виде анонимных блоков и сделала работающий общесистемный пул тредов — GCD. А это ключевые компоненты асинхронного программирования. Си — низкоуровневый язык, компилируемый в нативный код, который работает очень быстро. Да и поддержка Apple стоит многого.
Главный же минус Си в том же, в чём и плюс — он низкоуровневый. А также совсем не функциональный, и без автоматического управления памятью. Вполне может оказаться, что на Си с блоками будет программировать одновременно и долго, и сложно, и багоопасно.
Удивлены? Я не случайно начал пост со ссылки на пост про node.js. Одна из мыслей Саймона в том, что Javascript совсем неплох, как язык для асинхронного программирования. Анонимные блоки кода там есть. Да и не отнять, что сейчас весь ajax пишется на Javascript именно в асинхронном стиле.
Минусы — та же императивность и, что наверное ещё хуже, отсутствие модульности. Если что и губит этот хороший язык прямо сейчас — это общее глобальное пространство имён и отсутствие стандартного способа сделать импорт библиотеки.
И вот оно — моё любимое слово последней недели (кстати, по-русски я её как-то сразу стал называть "кложура" с ударением на последний слог). Про Clojure я узнал у того же Тима Брея. Потом пошёл на сайт, посмотрел на несколько выступлений Рича Хики об этом языке, и всё — по уши в него влюблён. (В язык, не в Рича :-) ).
Clojure — это Лисп. И для меня это плюс. Когда-то я лиспами заинтересовался, и стал тем, кого "скобочки не напрягают", мне очень нравится идея code-as-data. Хотя я понимаю, что это не для всех, но вы, ребята, сами виноваты :-).
Однако, уж если я вменил архаичность Erlang'у, то про Лиспы можно сказать то же самое, если не в большей степени. Но Clojure — это современный Лисп. Там есть не только списки, но и например нативные векторы и словари (задаваемые литерально фигурными скобками). Там нет проблем с юникодом (о чём чуть позже), и там много удобных макросов. То есть это Лисп, который придумали недавно, а не в конце пятидесятых.
Главная же фишка Clojure — это ориентация на многопоточность. Все структуры данных там неизменяемы (да, словари тоже), что даёт возможность распараллеливать доступ к ним вообще без локов. Эти неизменяемые структуры данных умеют очень дёшево дублироваться без использования полного копирования, что делает их быстрыми. И в языке есть несколько синтаксических конструкций, позволяющих явно задавать асинхронное выполнение и транзакции для связанных изменений. Всё это обязательно стоит послушать и посмотреть в оригинале в выступлении Рича Хики про параллельность. Оно длинное, но того стоит — взрывает мозг напрочь!
Хотя есть у Clojure и субъективный минус: она реализована на JVM. С одной стороны это очень практично: куча библиотек, готовая инфраструктура тредов, сборки мусора и JIT-компиляции. Очень правильный юникод, опять же. Но у меня ко всей этой инфраструктуре есть субъективная нелюбовь. Несмотря на то, что JVM уже много лет, она всё ещё вызывает проблемы при установке, всё ещё занимает немеряно ресурсов, и GUI-приложения на ней всё ещё не выглядят адекватно ни в одной десктопной среде (или я ошибаюсь?). Впрочем с практической стороны это всё не так страшно, если вести речь о веб-приложениях. Там все эти потроха пользователям не видны.
Так что официально заявляю, что в будущей гонке за процессорное время мой фаворит — Clojure.
Забавно поворачивается история, однако. Когда-то, когда Windows 95 пришла на смену Windows 3.11, одним из главных преимуществ новой архитектуры было то, что вместо кооперативной многозадачности, когда каждое приложение должно было само давать работать другим, применялась вытесняющая многозадачность, когда время на процессы жёстко распределялось системой.
А теперь посмотрите, куда мы пришли. Выясняется, что внешняя система не в состоянии рулить своими процессами максимально эффективно, не зная их семантики. Хочешь независимого исполнения — делай копии всей памяти на каждый процесс, даже если каждому из них нужны лишь мизерные её доли. Жалко памяти — обвешивай доступ к любым системным объектам локами. И не получится оптимизировать раздачу времени процессам так, чтобы они по возможности использовали независимые ресурсы системы. Потому что для этого надо знать, что конкретно процессы собираются сейчас делать.
Поэтому мы снова возвращаемся к тому, что процессы сами должны управлять своим распараллеливанием. А это — та самая кооперативная многозадачность. Хотя, конечно, существенно более сложная и безопасная, чем в Windows 3.11, и с другими языковыми средствами. Но забавно :-).
Комментарии: 85
Очень хорошая заметка :)
А в чем, по вашему, архаичность Эрланга?
Я бы еще в плюсы кложуре записал то, что она может связываться с другими языками, крутящимися поверх JVM, особенно со Scala, который (имхо) тоже имеет большое будущее.
К вопросу о Haskell - без дополнительных действий (явного указания/специальных структур данных) его нельзя параллелить, хотя бы потому что ленивость используется для бесконечных списков.
Вообще асинхронность и тредность - это парадигмы, которые имеют разные области применимости.
Тредность - мало параллельных задач, задачи долгоживущие. Например, необходимость делать долгие вычисления не замораживая пользовательский интерфейс. Т.е. задач априори больше чем процессорного времени, цель - распределить время равномерно, накладные расходы не очень беспокоят.
Асинхронность - много параллельных задач, задачи короткоживущие. Например, вебсервер. Задач меньше чем процессорного времени (иначе покупается еще один сервер), цель - увеличить эффективность использования процессора.
Поэтому обязательно останутся обе парадигмы. Однако в такой специфичной области как веб-программирование очевидно произойдет смена парадигмы с тредной на асинхронную.
Ну, добро пожаловать в клуб :-)
Я с clojure в ленивом режиме уже с пол-года вожусь. Очень хорошая вещь :)
чтобы получить анонимные блоки в питоне надо совсем немного подхачить парсер
Выскажусь в пользу Эрланга (ну делал я на нём диплом, обожаю этот язык :)). Выше уже спросили: в чём, по-вашему, архаичность Эрланга? На мой взгляд это вполне современный язык с достаточным количеством библиотек и более-менее вменяемым Unicode в последних версиях. И обязательно нужно учитывать, что Эрланг это целая платформа OTP, а не только язык. Дело просто в том, что, программируя на Эрланге, следует думать совершенно иначе. Лямбда-исчисление это в первую очередь логичная и аккуратная математика, которую перенесли в высокоуровневый язык программирования. Эрланг очень прост, в нём нет много того лишнего, что есть в других языках (читай: императивных), от этого может показаться, что он беден возможностями и архаичен, но это не так. Вы же не говорите, что HTML архаичен, хотя в сущности он позволяет сделать только одну вещь: описать структуру документа. Эрланг в этом плане такой же. Он очень и очень прост и позволяет сделать только одно: описать желаемый результат. Причём в терминах абсолютной распараллеленности. На мой взгляд, если где и будущее — то именно здесь. Clojure может и крут, но JVM напрочь убивает все плюсы. Виртуальная машина Эрланга же чудовищно надёжна и позволяет делать горячую замену кода (причём даже нативных функций, написанных на С). Всё, чего там не хватает — классного веб-фреймворка а-ля Джанго. А веб-серверы на чистом Эрланге уже есть (целых два) и они показывают отличные результаты, JSON давно присутствует + Mnesia и CouchDB. Ну и возможность удобно и надёжно контролировать всю инфраструктуру.
P.S. Ноги у Эрланга растут из Пролога, кстати. А ещё есть вполне рабочий и живой веб-проект на Эрланге: http://risovaska.ru/
1) Zotonic
2) Chicago Boss
http://narwhaljs.org/modules.html
Этот способ конечно не build-in в самом JS, но сильно лучше чем ничего.
А мне нравится под eventlet писать в синхронном стиле, но IO под капотом асинхронное. В эрланге, хаскеле и node.js это "из коробки", без дополнительных библиотек.
"Писать в асинхронном стиле" это уёбищный Twisted:
Иван, пожалуйста, не надо призывать нас писать в таком нечитаемом стиле. Асинхронными должны быть библиотеки; но это не стиль, это внутреннее устройство библиотеки. А писать как-раз удобно и понятно в синхронном стиле.
Кстати, в eventlet есть т.н. monkey-patching модуля socket. После этого традиционные синхронные либы на чистом питоне (например, urllib, myconnpy, pg8000) работают асинхронно. Да, либы с сишными ускорениями не пропатчишь на лету, увы.
Да, и самое главное, опять же упоминаемые Erlang и Haskell, а также Go, python-stackless, позволяют использовать именно потоки. "Традиционную" многозадачность. И всё нормально, нет проблемы 10к потоков. В двух последних есть проблема 10к connections. Она решается библиотеками.
Если учесть, что это благодаря легким потокам и асинхронному IO в runtime библиотеке языка, будет повтор предыдущего тезиса:
Возможно писать простой и понятный (потоки/процессы) код, который нормально работает с тысячами соединений.
Для этого нужно две вещи: легкие потоки и асинхронное IO. И эти фичи обязательно должны быть совмещены друг с другом на уровне языка, может быть даже синтаксиса. python-stackless даёт одно, а Twisted другое. По отдельности - убожество. А в том же Erlang эти две фичи совмещены, на лицо удобство и радость разработчиков.
P.S.: я не пропогандирую Erlang, мне тоже не нравится его архаичность, отсутствие документации, нормальных map(dict), record и, порой, стрёмные сообщения об ошибках.
что ж, поздравляю, видимо, ты пропустил golang с его goroutines как ещё одно решение проблемы многопоточности.
http://golang.org/doc/effective_go.html#concurrency
Прости, а вариант "посмотрел и не понравилось" не рассматривается? :-).
А еще у эрланга есть rabbit-mq (реализация AMQP)
И этот же AMQP очень помогает распараллеливать задачи используя питон. И есть Thrift. И тот факт, что люди меняют язык не очень охотно, скорее приведет к тому, что появятся библиотеки, помогающие распараллеливать программы намного проще, не меняя язык - надо под два ядра - запусти два питона. Просто может необходимость изучения новых "сложных" библиотек, приведет к росту доли "заточенных" языков.
Хотя это так, абстрактное рассуждение, даже без особого обдумывания темы.
Что понимается под "clean slate" в фразе "Node represents a clean slate"?
А что думаете о языке F#?
Yuri Baburov, в Go просто лёгкие потоки. Как в Erlang, Haskell и python-stackless. Это позволяет моделировать задачи в стиле "на каждый чих по потоку". При наличии хорошего шедулера на реальные OS потоки, это позволяет очень эффективно использовать проц.
Но это пол беды. Для эффективных приложений, которые в ожидании IO проводят больше времени, чем в вычислениях, ещё необходимо использовать асинхронный ввод-вывод; когда вы ждёте не каждый read/write, а сразу большую пачку и обрабатываете тот, который произойдёт раньше. Это позволяет эффективно использовать диск/сетевую карту.
Одно автоматически не означает другое. Например, в python-stackless (если я не ошибаюсь) когда один тасклет начнёт писать в сеть, он заблочит все остальные. Лёгкие потоки есть, а асинхронного IO нет. В nginx, Twisted наоборот. По-моему, в этом Иван слегка ошибся. Кинул в одну кучу наезды на толстые OS потоки и на блокирующийся IO и сделал удобный вывод. А ведь это ортогональные вещи, если рассматривать их отдельно, предложенные выводы не столь очевидны и последовательны.
Насчёт маленькой комьюнити Эрланга это Вы зря, последний год растёт как на дрожжах. Эрланг пиарят все кому не лень, начиная от упомянутой CouchDB и заканчивая 37signals, которые на нём Campfire написали. По популярности, размеру комьюнити и шумихе Erlang уж точно превосходит упомянутых Вами конкурентов (не считая JS конечно:)
Очень задела фраза о том, что JVM - это недостаток :( да это просто "state of art" среди виртуальных машин с лучшим в мире GC.
А питон надо поднимать, потому что нет другого языка, на котором было бы так приятно программировать. Надо срочно брать и писать свою реализацию Python 3k под V8 :)
Сергей, а это: http://posted-stuff.blogspot.com/2009/04/stackless-python-green-threading-and.html ?
Сергей, полностью поддерживаю вас насчёт eventlet, правда я сейчас больше склоняюсь к gevent от Дениса Биленко.
Иван, жаль, что вы не написали, каким образом в Clojure реализует конкурентное исполнение, как в этом ему помогает runtime и прочее.
Насчёт Erlang вы очень неправы — коммьюнити у него совсем не маленькое, да и язык довольно современен, к тому же OTP — это просто отличный фрэймворк для написания распределённых конкурентных приложений.
Кстати хочу заменить, что Erlang и Haskell (я имею ввиду GHC) впервую очередь отличаются и тем, что планировщики там не только коопертивные, но и вытесняющие. Это значит, что долгими вычислениями без I/O не остановить работу других в "потоках" — планировщик вытеснит текущий "поток" после n-го количества редукций в случае с Erlang или после n-ых тиков таймера в GHC.
Для меня java в сравнении с dot net покрылась паутиной :-) А если по теме то F# имеет кучу интересностей в эту сторону типа неизменяемых даннх, асинхроных workflows, мailbox proccessor и т.п. Также для всех языков на дот нет есть Parralel extensions library позволяет декларативно использовать асинхронность. Ну и исследовательский проект от MS под названием Axum. Некоторые отмечают похожесть на Erlang но это немного другая концепция.
Да в догонку Singularity как раз и демонстрировала потенциальную возможность написать все с нуля и продемонстрировать более эффективную асинхронность. Дешевые потоки и т.п.
Интересно, спасибо.
Не увидел упоминания Stackless Python. Там тоже было уделено внимание легковесной многопоточности (greenlets). Когда последний раз смотрел, там даже были какие-то асинхронные обертки над сокетами и, возможно, I/O. Более основательно, по-моему, этими проблемами заняты товарищи из проекта PyPy.
Да, в общем-то, эти возможности сейчас почти в любом новом ЯП есть - Scala actors, Google Go coroutines, ...
Кстати о Haskell. В данный момент разработка этого языка спонсируется Microsoft так как автор трудится над ним в составе MS Research. И позиционируется в первую очередь как экспериментальный не для продакшн. А все продакшн реди фишки будут плавно перекочевывать опять таки в F#. Уфф извините за кучу сообщений, за живое задели :-) я умолкаю.
http://wiki.commonjs.org/wiki/CommonJS — хотя это пока только "начинание"
cybergrind, спасибо, теперь знаю официальную позицию stackless по этому поводу. Так вот, там буквально написано, что вот вам специальный костыль, делайте monkey-patching модуля socket и получите non-blocking IO. То есть в python-stackless поддержки async IO нет и есть средство для решения этой проблемы. А теперь попробуйте придумать причину по которой этот патчинг не делается автоматически и пользователям дают возможность наступить на грабли заблочив все тасклеты без патчинга socket.
Ну и зачем тогда нужен отдельный интерпретатор, если ровно такой же подход работает с обычным CPython + eventlet? Скорость переключения потоков будет такая же, потому что eventlet использует greenlet - выжимку stackless. Сотни-тысячи сокетов eventlet (некоторые хабы) будет обрабатывать намного эффективнее, чем asyncore со своим select.
Hodzanassredin, лично по-моему, много полезных сообщений это хорошо, а MS-промывание-мозгов - плохо.
Java не может сравниваться с .NET. Как ядро Linux с Windows Server. Как Firefox c libcurl. Если у вас есть аргументированное сравнение JVM с .NET CLR - вот это интересно.
Там тоже message-passing и актёры, но немного другой, да? Axum это Erlang с доступом к .NET библиотекам. Вот когда сделали C# - копию жабы, да, надо было отрекаться, потому что скопировали говно. Здесь не надо отрекаться. Хорошую вещь скопировали.
А разработка Python спонсируется Google, т.к. Гвидо там работает, да? Это два разных факта; причинно-следственная связь, мягко говоря, натянута.
P.S.: F# - действительно хорошая, полезная штука. Если C#-говнокодеры выучат immutability, message-passing и монады, может и правда, что-то хорошее выйдет.
P.P.S.: всем остальным: извините, не удержался.
Иван, ещё, кстати, как-то в ЖЖ пролетала такая фраза, что раз в 10 лет надо переписывать заново весь стек: OS, библиотеки, сервисы, прикладные программы, опердень, всё. Тоже думаю, что это скорее не проблема, а решение.
Давайте всё же в конструктивных рамках оставаться.
Дык я о том, что тебе много чего не понравилось, но ты об этих альтернативах написал :)
А вчера сайт лежал что ли? Не мог зайти.
to Сергей Шепелев да промывка мозгов это плохо. Каюсь. Но я дот нет разработчик, я аддиктед :-)
1. Своей фразой про яву и паутину я хотел подчеркнуть свои устаревшие знания в области явы. Прошу прощение за некорректный Русский язык у меня была тройка.
2. Axum это язык который вводит понятие доменов. В пределах домена это обычная работа с объектами в стиле ООП. Но границы домена можно преодолевать только с помощью каналов. Концепция каналов по словам разработчиков была взята ими из самой большой в мире распределённой системы Web :-) Я думаю нет смысла писать подробнее так как любой может пойти на википедию и прочитать более правильное описание.
3. Действительно натянул как то не оттуда и не туда. Этим выражением я хотел подчеркнуть что мс заинтересованна в развитии Haskell и создатель языка тесно работает с ребятами из команды F#. Результаты совместной работы уже можно видеть сейчас например посмотрев на те же async workflows которые появились в f# благодаря влиянию Haskell. Так что можно надеяться что в будущем F# получит довольно интересные способности.
Кстати Simon Peyton-Jones(создатель языка Haskell) совместно с Tim Harris(тоже получает бабло от MS :-) ) трудились над Software Transactional Memory платформой которая в скором времени будет интегрирована в dot net.
Саймон иногда общается с Joe Armstrong вот кстати видео на эту тему http://www.infoq.com/interviews/armstrong-peyton-jones-erlang-haskell просто шикарные дядьки :-)
Также можно взглянуть на:
http://blogs.msdn.com/sos/archive/2009/07/23/Dmitri-Soshnikov-Interviews-Simon-Peyton-Jones-on-Functional-Programming-and-Haskell.aspx
P.S. насчет быдло кодеров: такова участь многих c# программистов в том числе и меня, дрова надо рубить и наточить пилу некогда.
Спасибо за статью. Кажется пришло наконец-то время Лиспа и Ко :-)
Непонятно только почему Erlang назван архаичным.. Странный синтаксис - это да, но архаичность..? Эрланг - сравнительно молодой язык со вполне прозрачным и довольно внятным синтаксисом. Несколько запутаны правила пунктуации, но это не смертельно (особенно по сравнению с Python).
Красота и мощь эрланговской модели не столько в асинхронности, а в том, что она позволяет прозрачно преодолевать сетевой барьер, то есть взаимодействовать с удаленными процессами, как с локальными. Clojure сосредотачивается на жонглировании агентами и отображением их на потоки => разные области специализации. Хотя Clojure'вская модель выглядит элегантнее.
Что касается производительности, то до сих пор у Тима Брея ничего хорошего с Clojure так и не вышло - слишком много уходит на координацию и коммуникации, из чего можно сделать вполне банальный вывод, что не все задачи стоит распараллеливать. То есть, нужно чтобы сложность коммуникационной части была а) существенно ниже вычислительной и б) не росла квадратично с числом процессов. Парсинг XML в DOM к этой категории, увы, не относится.
Совершенно согласен с тем, что JVM для Closure - это тяжкое бремя, а вовсе не преимущество. Сам язык - да, функциональный, но платформа - то есть библиотеки и VM - нет. Что делать с блокировками на уровне платформы - идей никаких нет. Будет ли Clojure жить если у него оторвать JVM - очень сомнительно, по крайней мере прецедентов таких не было.
Поэтому мои ставки - на Erlang или на какой-то новый язык, но не Go, он слишком низкоуровневый. Возможно это действительно будет Javascript с подчищенным синтаксисом.
Я тут слышал, что и Google и Yahoo подсаживаются (или уже подсели) на Erlang. По-крайней мере, в России, я точно знаю есть достаточно крупные проекты, где Erlang взят на вооружении, да на столько серъёзно, что в одном из них собрались спонсировать МГУ-шные курсы по Erlang-у.
При таком раскладе вывод насчёт архаичности кажется скоропалительным.
речь о лябде?
дык чем описанная рядом функция от нее отличается?
да ничем!
и GIL не так страшен как его малюют.
LISP явно не для средних умов, как тот же хаскель.
а в чем хитрость? магия?
кто мешает тебе такой же трюк сделать, например, на плюсах?
и главное.
по-моему, в статье спутано до кучи асинхронное (event-based) программирование и такая линейная запись алгоритма, которая при исполнении может быть автоматически раскинута на несколько ядер. это разные вещи.
упрощение асинхронного программирования на уровне языка действительно очень важно, т.к. обычно такой алгоритм в разы сложнее линейного кода.
Текущая популярность языков: http://www.google.com/trends?q=haskell%2C+erlang%2C+clojure&ctab=0&geo=all&date=all&sort=0
Haskell и Erlang - это не только названия языков, плюс, это совсем не популярность. :-)
А по-моему вы все рано стали плясать на костях Python. Его чистый и простой синтаксис делает его очень перспективным.
Не забываем про IronPython, Jython - ничто не мешает засунуть под капот любую другую реализацию.
Обмен же сообщениями, асинхронность - это все легко делается. И Twisted не так страшен, каким кажется тем, кто его не до конца понял.
лисп простой. вот хаскелль да, требует определенной матподготовки.
stackless + stacklesssocket + epoll:
http://osdir.com/ml/python.stackless/2008-04/msg00009.html
в общем то написать любую свою либу для stackless + epoll - нет проблем (ну если вообще с неблокирующими сокетами имел дело).
но в общем есть такая штука - асинхронность это еще не все. распараллеливаемость - тоже очень много. сейчас выпускают core i9 процессоры, в них 6 ядер. на серверах не редки случаи 8-16 ядер. сам по себе питон это богатство действительно неможет никак использовать, только танцы с бубном - у меня запущено хез сколько интерпретаторов, которые подключены к AMQP, но на больших нагрузках (20кк сообщений) система все равно висит. и оперативы все это дело съедает порядка гига (это только интерпретаторы). Но зато действительно параллельно и асинхронно (amqp через твистедовскую реализацию). В последнее время стал задумываться над тем, что даже простой перенос с питона на С принес намного большую производительность (микросекунды против миллисекунд). Пока перетягиваю куски на хаскель (одно конфигурирование под количество потоков добавлением +RTS +N4 - уже нечто), но дело пока туго идет =)
p.s. предыдущий случайно раньше отправил, можно, пожалуйста, его удалить? и нету тут open id через gmail account?
Кстати, какая сейчас в русском принята терминология, чтобы различать concurrent и parallel programming? Многозадачное программирование и параллельное программирование?
cybergrind, спасибо за аргументы. А на BSD надо будет обвзязывать на kqueue. Ту же самую задачу выполняет eventlet libev/libevent hub. Всё сводится к вкусу фломастеров. :)
Кстати, питон может использовать ядра. threading даёт именно OS поток, которые скалируются на ядра. Я проверял и получал на двухядернике >100% в htop. Другое дело, что из-за низкой эффективности переключения питонских потоков и GIL накладные расходы настолько велики, что один поток нередко справляется лучше, чем два :)
По поводу нагрузок под AMQP/Twisted предлагаю поразмыслить вот над чем: некто жаловался, что у них в нормальном коде (без багов) кончался лимит стека (1000). Увеличили до 2000, всё работает. Вот такой твистед. Но, конечно, с бешеным числом сообщений уже и сама медленность питонского интерпретатора может быть ботлнеком.
Кнопки Google account нет, увы. Но вы можете ввести URL https://www.google.com/accounts/o8/id.
Думаю, что под архаичностью эрланга чаще всего подразумевается синтаксис пролога.
ну у меня recursion limit вообще никогда не исчерпывался, но, реализацию txAmqp я не люблю за @inlineCallbacks(ну ненравится мне как там реализовано все, особенно возврат значения через raise).
а многопоточный питон делать смысла не имеет, разве что через multiprocessing. найденные ссылки на тему:
http://backyardbamboo.blogspot.com/2009/02/python-multiprocessing-vs-threading.html
http://stackoverflow.com/questions/1289813/python-multiprocessing-vs-threading-for-cpu-bound-work-on-windows-and-linux
т.е. очень даже впечатляет, особенно по сравнению с простым тредингом. На той конторе, которой я сейчас работаю, большую нагрузку разрулили через coroutines + non blocking sockets(а еще написали самую быструю сериализациюдесериализацию из того что я видел для питона).
но в общем то все решения - запуск большого количества интерпретаторов, незнаю как у кого, а у меня они получаются довольно тяжелыми и десяток-второй убивает пол-гига, а иногда и гигабайт оперативы, но это не страшно, учитывая сколько выжимается из этого дела. Но вот когда делаешь быстрые реализации, и видишь насколько они быстрее чем питоновские, начинаешь задумываться.
А почитав материалы по поводу увеличения производительности, приходишь к выводу - достаточно писать асинхронные приложения, а распараллеливать уже на разные процессы - т.к. обычно такие распараллеливания очень хорошо горизонтально масштабируются (т.е. смог разнести на два процесса - можешь разнести на разные машины). В этом плане очень круто выглядит эрланг - который сам по себе может разносить задачи по разным машинам. Но, безусловно, срывает крышу обилие вариантов у хаскеля(до сих пор не понимаю, почему это не мэинстримовый язык, учитывая то, сколько там всего реализовано).
сорри что как то сумбурно, почти час писал
Кстати елси не нравится Closure на JVM ведуться работы по портированию на CLR
Посмотреть можно тут - http://github.com/richhickey/clojure-clr/
Вики с описанием (хоть каким то) здесь - http://wiki.github.com/richhickey/clojure-clr
если сильно хочется померяться рейтингами популярности, то вот: http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html
Повторю, что уже говорилось выше: в статье несколько спутаны две парадигмы - треды и асинхронность. А поиск подходящего языка нужно вести, определившись с нужной парадигмой. Для веба пророчат асинхронность - ОК, но чего тогда огород городить-то с GIL'ом?
Что же касается "отсутствия полноценных анонимных блоков", то это очень непринципиальная проблема, которая имеет отношение разве что к читабельности (просто определяем неанонимный блок кода тут же, не отходя от кассы - пайтон это позволяет - и используем его). Проблема здесь кроется в другом. В отличие от Javascript, Python не имеет полноценных лексических переменных, что существенно ограничивает возможности callback-функций (по крайней мере, заставляет неочевидно извращаться с классами или генераторами). Хрестоматийный пример: http://www.paulgraham.com/accgen.html
cybergrind, я вот что подумал, внезапно. Надо собрать питон без поддержки тредов. Тогда в нём не будет GIL и процессы будут работать немного быстрее.
cybergrind, мы тоже всё делаем (хм.. ну я пытаюсь заставить) через coroutines + non-blocking sockets. Конкретно на eventlet, благо он всем понравился, а в 0.9 и баги пофиксили. Жаль только, что нет таких нагрузок, где б это действительно было нужно.
Это не так.
Хаскель сложен в основном из-за его системы типов. Монады - их часть. Но монады (как и большая часть остальных в нем вещей) - это не часть языка. На нем можно эффективно прогать, не зная про это все. Надо лишь понимать как работают конкретные вещи: Reader, State, Maybe.
Людей пугают монады и теория категорий, им кажется что все, приехали, надо продираться через ужасные дебри математики.
На самом деле, если не думать о сложностях и писать исходя из имеющихся примеров (мне лично хватило книги Real World Haskell), то довольно быстро набираешь необходимый практический базис, чтобы разбираться в системе типов. Плюс еще надо расширить своё понимание ООП.
Я также как и вы думал пару лет назад. я его все это время время в фоновом режиме изучал... Но потом попалась книга RWH. И все стало просто.
Интересная заметка. И комментарии интересно читать.
Одного вот только понять не могу. Зачем "переписывать все"? Я понимаю что асинхронная модель эффективна для IO. Но для CPU bound задач (например, тот же парсинг XML) полезность асинхронной модели под вопросов. Отталкиваться от этой модели при поиске удачных решений по распараллеливанию CPU bound задач не лучшая идея, на мой взгяд.
Кстати, если вы погуглите, то найдете по меньшей мере два документа описывающие распараллеливание парсинга XML. Ни в одном из них асинхронная модель не используется.
Денис, асинхронная модель при парсинге не используется, потому что IO не имеет отношения к парсингу :) сферический парсинг в вакууме предполагает, что у нас на входе непрерывный поток байтов с нулевой задержкой на чтение следущего байта. Иван правильно пишет, ждать окончания парсинга перед чтением следущего куска байтов бессмысленно, надо читать как только можно (пока памяти хватает), но это имеет отношение к вводу, а не к парсингу. Тут уже раза 3 делали вывод, что он в одном посыле смешал неблокирующееся IO и распараллеливание. Отсюда и конфуз.
Я так думаю не просто так. Я не могу программировать, когда не знаю теоретическую базу под языком, что как и почему в нем сделано. Если с питоном тем же до таких вещей добираешься когда уже активно включился в работу (например, далеко не всем вообще нужно знать как конкретно работают del/new в питоне и т.п.), то с хаскеллем это сразу. И возникает естетсвенное желание понять что там да как. Мне было проще, я по профессии математик, но и мне все далось не сразу, учитывая то, что такие экзотические языки учат в фоновом режиме. Поэтому я и утверждаю, что хаскелль сложен для "человека с улицы". И уж он, очевидно, сложнее чем ЛИСП, потому что в лиспе нету ничего :) Там нечему быть сложным. Пара принципов и все по большому счету.
Что-то Вы ошибаетесь:
И, кстати, JS в этом плане гораздо менее удобен, особенно в случае, когда вам нужно создать массив замыканий в цикле.
Я ещё один пост скоро сделаю про этот момент. В этой области сейчас с терминологией, что русской, что английской, действительно хреново :-)
xonix, я, честно говоря, не понимаю, чем так плохи питонские лямбды (разве что экзепшон нельзя кинуть).
Но вы схитрили. Там в правилах чётко написано, что нужно инкремент сделать, а не сложение. Для этого вам нужно будет сделать setattr объекту на который замкнута создаваемая лямбда. Как его получить - без понятия.
Именно!
То, как решена задача по ссылке, я считаю - самый pythonic способ. Просто постановка задачи не способствует его нахождению. :) Повторю это решение здесь:
Если же делать с помощью замыканий:
Генератор тоже можно приспособить:
Выглядит притянуто за уши, но помнится, где-то мне пригодилось замкнуть возвращаемую функцию на генератор (правда он был не бесконечный и чуть посложнее, чем здесь), так что пример не совсем лишен пищи для ума. И еще один, just for lulz (так бы пришлось делать, если бы не было метода send()):
Возможно, все это - оффтопик, но иллюстрирует мою точку зрения по поводу процесса создания функций (в т.ч. callback, которых в асинхронном коде придется писать много) "на лету" в пайтоне.
Если смотреть глубже, то как насчет поменять и ОС в том числе, например, на Inferno?
По теме, IMO, главая проблема не неэффективном многозадачном программировании, а в неэффективном программировании вообще. Любой проект рано или поздно достигает стадии, когда "надо все переписать" и эффективных решений этой проблемы пока не предложено. Все предложенные идеи (например здесь http://www.vpri.org/html/work/ifnct.htm)) пока не материализовались во что-то юзабельное. Поэтому сегодня все интересные проекты держатся на одной светлой голове и максимально возможном оттягивании 1.0 релиза, потому что потом backward-compatibility и смерть.
@Nick Lutsiuk
Что подкрепляет мой наезд на Twisted в частности и на призыв писать "код в асинхронном стиле" в целом:
Не надо писать макароны колбеков. IO должно быть асинхронным, а код, его использующий - последовательным.
Весь мой посыл как раз в том, что "писать код в асинхронном стиле" — это не то же самое, что "макароны колбеков". Если ваш язык сдизайнен для этого, то там всё пишется и читается прямо. Питон, кстати, не для этого.
Я ставлю на JS. Clojure — это, наверное, хорошо, но (эти (ваши (бесконечные скобочки))) и (слов (порядок (нечеловеческий (ваш этот))))… Проблема в том, что ни один из р-р-революционных ЯП не обладает человеческим синтаксисом, а значит, страшно далеки они от народа. Ну не нужны сейчас нафиг все эти ожившие мертвецы типа Лиспа… Вот ЗАЧЕМ там именно лисп? Если основной поинт — это неизменяемые структуры данных и транзакционная память — зачем это делать именно в этом древнем синтаксисе? Ну ещё бы Форт вспомнили, ей-богу.
JS — отличный язык, крайне лаконичный и фантастически мощный, просто удивительно как такой шедевр могли сочинить в ответ на банальное ТЗ «сделать что-нибудь, чтобы в браузере картинки менялись». Node.js имеет в основе идею (отказ от синхронности), достаточно безумную, чтобы завладеть творческими массами:) Желаю ему всяческого успеха.
Давид, вы пропустили один момент - ну зачем, зачем у JS эти страшные скобочки {}? По-моему синтаксис C не менее древний.
Иван, а что именно в питоне не для этого? Что предрасполагает к макаронам колбеков?
Заступлюсь за питон в целом и за twisted в частности. Мы очень давно пишем на twisted в асинхронном стиле и за все время код не был испорчен ни единым коллбэком. Этого удалось добиться с помощью генераторов питона 2.5. Вот простенький пример:
То есть оператор yield выполняет роль подождать окончания процесса для Deferred и роль return для простых значений. Нечто подобное позже сделали в inlineCallbacks, но там вместо yield во втором случае используется raise.
YuppY: Вопрос от не-питониста: не является ли это своего рода хаком, затрудняющим понимание программы? Ведь yield, вроде бы, немного для другого предназначен?
Сам yield предназначен для понятия "отдать управление назад, сохранив точку выполнения". Чаще всего он используется для итерации, но и такой способ применения вполне законен. И красив :-).
YuppY, Вы сами написали
@webtwist.deferred, или это какой-то "штатный" декоратор Twisted? И по какому принципу работает ожидание?Не-не... вы правы и хаскель действительно сложен.
Я лишь говорю что матподготовка необязательна. на моём примере тык сказать.
Сложность хаскеля - это фича. Его изучение дает понимание в смежных областях. Его понимание дает большие бенефиты в понимании "Сути вещей".
Т. е. быдло-кодеры на хаскеле невозможны по определению и сути.
Дмитрий, смотрите реализацию inlineCallbacks (twisted.internet.defer)
в двух словах, декоратор получает управление на yield'ах и заворачивает в deferred вызовы(надо чтото сделать асинхронно - ты просто делаешь yield того, что хочешь делать асинхронно). проблема скорее возникает когда ты хочешь чтото возвращать по выходу из функции - вот там это действительно некрасиво, все помним что нельзя использовать return в генераторах? в твистеде возврат значений сделали через raise.
да и вообще, на мой взгляд, не совсем корректно говорить, что питон не создан что бы можно было писать каким-то определенным образом, просто некоторые вещи "под капотом" будут иметь сложную реализацию, но не более того (вы только посмотрите сколько охренительных вещей сделано на java. а ведь рядовой пользователь спринга может и не представляеть как оно работает внутри). если будет надо, ведь можно писать на си к питону, да хоть хаскель встроить =)
Давид, это не хак, про такой способ применения разработчики питона даже PEP написали: http://www.python.org/dev/peps/pep-0342/
Дмитрий, webtwist.deferred мы написали сами, но inlineCallbacks от него по сути ничем не отличается. Если не нравится return через raise, то там достаточно подправить несколько строчек.
Недостаток питона в отсутствии библиотек, способных работать асинхронно. Например, чтобы медленный запрос mysqldb не блокировал обслуживание других соединений, приходится выносить его в специальный пул тредов (twisted.internet.threads.deferToThread).
Вот тут еще вспомнилось: http://local.joelonsoftware.com/wiki/Опасности_обучения_на_Java
У YuppY в твистедном примере нет колбеков. Это отлично. И по-моему, это действительно пример "асинхронного стиля", потому что управление контроллируется программистом при помощи yield. Забыл написать yield - получил по башке дефередом.
Предлагаю обсудить такой тезис: в чём вообще оправдание синхронного IO? Почему эти декораторы и yield надо явно писать? В eventlet/др. не надо. В raw-Python/Twisted можно, а в eventlet/др. нельзя написать явно блокирующийся IO вызов. Эта возможность прострелить себе ногу где-то нужна? Потому что я не вижу где бы это было полезно. Одни проблемы.
Спасибо за ответы, хорошая пища для размышлений.
Да вот, кстати, http://code.google.com/p/coev/
и не надо состязаться в написании колбеков наиболее читаемым способом.
Если необходимо решить задачу многозадачности, то самый простой способ вижу в создании потоков. Проблемы с нехваткой памяти дешевле и проще всего решить приобретением дополнительных модулей.
Больше всего в twisted напрягает то, что с колбеками ты пишешь практически в continuation passing style. А на питоне это ой-как неудобно без лексического замыкания binding'ов (переменных). Поэтому очень хотелось чего-то подобного, реализованного в Common Lisp'е. Но javascript с нормальными замыканиями нам подходит :)
Во-первых, соглашусь со Слоном про то, что асинхронные функции и потоки имеют разную направленность и будут жить бок о бок. В моём мире не-вёб программирования без потоков обойтись не получится.
Во-вторых, замечу, что блокировки - это не единственный способ синхронизации. Весь этот хайп вокруг lock-free не просто так поднялся. Да, сложно, но зачастую может прятаться вглубь библиотеки. А выигрыш даёт во многих случаях очень заметный. Кстати, насколько я знаю, JVM - одна из самых напичканых lock-free систем. Сановцы эту тему разрабатывают ещё с тех пор, когда компьютеры были большими.
Про "проблему 500". Тут ещё может быть проблема с тем, что переключение процессов/потоков - это дорогая операция. И в идеальном мире на каждом ядре должен крутиться лишь один поток. При слишком большом количестве потоков lock contention и оверхэд переключений будет гасить все преимущества многопоточности. Ведь ядро-то одно, и в каждый момент всё равно выполняется только один поток.
Александр Сабуренков, спасибо вам огромное за ссылку на coev. Очень интересный проект.
По поводу программ как данных, что думаете о связке XML+XSLT+XQuery (последнее 2 можно автоматом генерить из тех же XML данных-деклараций). Все хранится и работает внутри Native XML СУБД eXist (http://exist-db.org)).
что значит отсутствие модульности в javascript? Про commonjs не в курсе (http://commonjs.org/)? Вот http://wiki.commonjs.org/wiki/Modules/1.0 описание модулей. В Node.js поддержка модулей реализована.
а как на счёт Lua ?
можно ли об этом ЯП программирования говорить, что он позволяет писать программы работающие в многопоточном стиле ?