Маниакальный веблог » Параллелизмhttps://softwaremaniacs.org/blog/category/parallelism/2019-03-06T00:12:43.041117-08:00ManiacИван Сагалаев о программировании и веб-разработкеhttp://softwaremaniacs.org/media/sm_org/style/photo.jpgАсинхронность и параллельность
2011-10-10T15:13:37.968000-07:00https://softwaremaniacs.org/blog/2011/10/10/asynchronous-vs-parallel/Давно-давно лежит у меня в почте письмо, которое всё никак не доходили руки опубликовать. Здравствуйте, прочитал ваши статьи про параллелизм. Возник вопрос: Асинхронность и параллельность это одно и то же? Если нет, буду благодарен за ссылку на ресурс, где об этом можно подробнее прочитать. Здравствуйте! Сразу скажу, что на такой ...
<p>Давно-давно лежит у меня в почте письмо, которое всё никак не доходили руки опубликовать. </p>
<blockquote>
<p>Здравствуйте, прочитал ваши статьи про параллелизм.
Возник вопрос: Асинхронность и параллельность это одно и то же? Если
нет, буду благодарен за ссылку на ресурс, где об этом можно подробнее
прочитать.</p>
</blockquote>
<p><a name=more></a></p>
<p>Здравствуйте!</p>
<p>Сразу скажу, что на такой вопрос невозможно ответить объективно, потому что терминология новая, и в разных местах употребляется по-разному. Поэтому могу предложить только своё собственное толкование, не претендуя на универсальность.</p>
<p>Понятия эти разные.</p>
<p><em>Асинхронность</em> говорит о порядке исполнения кода. Если вызываемая функция не возвращает значение сразу, а отдаёт управление вызывающему коду с обещанием выдать значение позже, то эта функция асинхронная. При этом нет никаких предположений о том, как это значение будет считаться: параллельно или нет.</p>
<p><em>Параллельность</em> говорит о том, что в машине физически происходит несколько процессов одновременно. При этом, с точки зрения кода программы это всё может выглядеть вполне синхронно. Например, если подряд вызываются две функции, то исполняющая среда может решить по каким-то признакам, что эти функции независимы, и выполнить их параллельно. С точки зрения программы этого заметно не будет.</p>
<p>На практике, конечно, эти понятия пересекаются очень часто, потому что асинхронные вызовы делаются как раз для того, чтобы исполнять функции параллельно с основным кодом. То есть, асинхронные вызовы — самый распространённый способ управления распараллеливанием на уровне кода. Неявную (или автоматическую) параллельность, про которую я написал выше, насколько я знаю, пытаются реализовать сейчас в Хаскелл. Правда, я не в курсе, на какой стадии этот проект. Несчастная "асинхронность"
2009-12-13T11:01:01-08:00https://softwaremaniacs.org/blog/2009/12/13/unfortunate-asynchronism/Знай я, что слово "асинхронный" вызовет такое непопадание в смысл, просто не стал бы его использовать... И несмотря на то, что я попытался определить термины в следующем посте, всё равно появляются комментарии про то, что я "путаю две вещи" и "асинхронность и multicore ортогональны". Постараюсь обясниться яснее и короче. Повторюсь ...
<p>Знай я, что слово "асинхронный" вызовет такое непопадание в смысл, просто не стал бы его использовать... И несмотря на то, что я попытался определить термины в следующем посте, всё равно появляются комментарии про то, что я "путаю две вещи" и "асинхронность и multicore ортогональны". Постараюсь обясниться яснее и короче.</p>
<p><a name=more></a></p>
<p>Повторюсь цитатой из предыдущего поста:</p>
<blockquote>
<p>Ещё один термин, которому не повезло — <dfn>асинхронность</dfn>. По-настоящему она означает вызов функции, которая не делает всей своей работы сразу, а быстро возвращает управление в вызвавшую программу, выполняя свои вычисления параллельно с ней. Никакого конкретного способа реализации такого параллелизма не предполагается.</p>
<p>Однако часто "асинхронность" неявно жёстко привязывают конкретно к асинхронному вводу-выводу, когда асинхронность вызова обеспечивается тем, что долгую и тупую часть операции по перекладке байтов на себя берёт отдельный сервис ядра. Чтобы отличать это понятие от "асинхронности вообще", я буду пользоваться тремином <dfn>неблокирующий IO</dfn>.</p>
</blockquote>
<p>Уважамые комментаторы! Я не писал пост про асинхронный <em>I/O</em>. Этот вопрос вполне себе решён библиотеками уже сейчас, и проблем не вызывает. Я писал про асинхронное всё остальное, чего у нас ещё нет. Про распараллеливание любых алгоритмов: начиная от элементарной сортировки и до алгоритма поведения игрового мира с сотнями юнитов. Я не хочу спорить про правомерность употребления слова "асинхронность" в этом контексте. Если оно вам не нравится, просто используйте слово "параллельность". Или "concurrency".</p>
<p class=strong><strong>Но только не пишите мне про ввод-вывод, пожалуйста :-)</strong>Параллелизм 2
2009-12-06T08:55:06-08:00https://softwaremaniacs.org/blog/2009/12/06/parallelism-2/Да, заголовок поста на этот раз скучный :-). Это продолжение темы, начатой в "Надо всё переписать", составленное в большей части по комментариям. Пользуясь случаем хочу сказать большое спасибо комментаторам! Написали много полезного и интересного, статья была бы неполной без этого. Терминология Как многие отметили, термины, которые я использовал, не отличаются ...
<p>Да, заголовок поста на этот раз скучный :-). Это продолжение темы, начатой в "<a href="http://softwaremaniacs.org/blog/2009/11/30/gotta-rewrite-everything/">Надо всё переписать</a>", составленное в большей части по комментариям.</p>
<p>Пользуясь случаем хочу сказать большое спасибо комментаторам! Написали много полезного и интересного, статья была бы неполной без этого.</p>
<p><a name=more></a></p>
<h2>Терминология</h2>
<p>Как многие отметили, термины, которые я использовал, не отличаются однозначностью. Попробую подробно объяснить, что я имел в виду. Я не настаиваю на своём прочтении этих терминов, они в принципе ещё совсем не устоялись.</p>
<p>Самое общее понятие — это <dfn>параллелизм</dfn>. Оно означает любое параллельное исполнение кода. Причём как "честное" — на нескольких процессорах/ядрах, так и имитируемое программно на одном процессоре. Способов его организовывать может быть несколько. Мой общий посыл относительно параллелизма как такового состоит всего-навсего в том, что теперь мы от него никуда не денемся.</p>
<p>Один из способов делать параллелизм — это <dfn>детерминированный параллелизм</dfn> (не знаю более короткого термина). Я совсем не касался его в прошлый раз, и поэтому сошлюсь на хорошо объясняющий пост "<a href="http://ghcmutterings.wordpress.com/2009/10/06/parallelism-concurrency/?ftw">Parallelism /= Concurrency</a>", из которого я понял, что речь идёт о случаях, когда у вас есть функция типа f(x, y), где x и y друг от друга не зависят, вследствие чего вы можете считать аргументы параллельно, и тогда значение f будет посчитано быстрее. Штука в том, что в императивном языке невозможно предсказать независимость x и y только на основании их аргументов, потому что каждый из этих процессов может менять состояние мира, в котором работает, и это может влиять на результат другого процесса. Зато в функциональных языках, где мир вокруг внезапно не меняется, эту параллелизацию, по идее, возможно делать автоматически.</p>
<p>Весь остальной — недетерминированный — параллелизм принято называть словом <dfn>concurrency</dfn>, которому я вменяемого перевода на русский язык не знаю. Недетерминированность concurrent-процессов происходит от того, что они обмениваются данными в непредсказумые моменты времени. Это могут быть разделяемые даные в общей памяти и может быть передача сообщений с данными между процессами. Причём как именно организованы эти процессы, не уточняется: unix-процессы, треды, процессы виртуальной машины, сервисы ядра и т.д.</p>
<p>В связи с этим стоит сказать про не очень удачное слово <dfn>тред</dfn>, под которым часто понимаются две разные вещи:</p>
<ul>
<li>Треды операционной системы или виртуальной машины, как просто способ реализации разных параллельных задач, без уточнения того, как именно их используется программа. Например: "агенты в Clojure исполняются в отдельных тредах JVM".</li>
<li>Широко распространённая модель программирования с использованием разделяемой памяти и локов для разграничения доступа к ней. Например: "когда тредов больше тысячи, всё загибается".</li>
</ul>
<p>Я очень стараюсь использовать слово "тред" только в первом смысле. Второе пусть называется "шаренные данные и локи".</p>
<p>Ещё один термин, которому не повезло — <dfn>асинхронность</dfn>. По-настоящему она означает вызов функции, которая не делает всей своей работы сразу, а быстро возвращает управление в вызвавшую программу, выполняя свои вычисления параллельно с ней. Никакого конкретного способа реализации такого параллелизма не предполагается.</p>
<p>Однако часто "асинхронность" неявно жёстко привязывают конкретно к асинхронному вводу-выводу, когда асинхронность вызова обеспечивается тем, что долгую и тупую часть операции по перекладке байтов на себя берёт отдельный сервис ядра. Чтобы отличать это понятие от "асинхронности вообще", я буду пользоваться тремином <dfn>неблокирующий IO</dfn>.</p>
<h2>Посыл</h2>
<p>Исходя из написанного выше, я теперь могу уточнить, что говоря о том, что придётся всё переписать в асинхронном стиле, я не имел в виду только неблокирующий IO. Можно без потери смысла сказать, что нам придётся всё переписать параллельно.</p>
<p>Вообще, ключевое слово там — не "асинхронно", а "придётся" :-).</p>
<h2>Разные виды concurrency</h2>
<p>Ещё одна важная вещь, которую я хочу подчеркнуть ещё раз, это то, что я считаю concurrent-программирование на императивных языках с разделяемой памятью и локами <em>неработающей</em> моделью для высокопараллельного будущего. И откровенно говоря, она и в настоящем уже достаточно проблем вызывает.</p>
<p>Именно поэтому мне интересны языки, которые реализуют concurrency по-другому.</p>
<h3>Передача сообщений</h3>
<p>В Erlang это независимые процессы с передачей сообщений между ними. Главный плюс независимости процессов — возможность запускать их на разных физических машинах, и значит масштабироваться практически бесконечно. Главный минус — потери производительности на копирование данных и на обслуживание этой самой независимости. Erlang'исты меня поправят если что, но кажется, даже для инкремента счётчика нужно запрограммировать отдельный процесс, который примет на вход текущее значение и вернёт на выходе увеличенное на единицу. Это накладно.</p>
<p>Ещё примерно тот же подход с передачей сообщений используется в Go, если я правильно читаю <a href="http://golang.org/doc/effective_go.html#concurrency">их доку по concurrency</a>. Для физической реализации параллельных процессов используются треды ОС. Причём кажется, что тред создаётся под каждую goroutine, никакого готового пула под них нет. Что не очень хорошо.</p>
<h3>Разделяемая транзакционная память</h3>
<p>В Clojure же всё наоборот. Там данные лежат в общей памяти, но никогда не изменяются. Вместо этого новая версия данных строится <em>над</em> старой, и шарит с ней все неизменённые куски. Раз данные не изменяются, доступаться к ним можно вообще без использования локов. А конфликты при записи новых данных решаются реализованной в языке software transactional memory, которая прозрачна для программиста и гарантирует корректность (хоть и недетерминированную) новых версий данных. Это очень хорошо видно в примере с эмулятором муравьёв, в <a href="http://blip.tv/file/812787">видео, на которое я уже ссылался</a>. Там в районе времени 83:45.</p>
<p>Главный плюс — это очень быстро и это наверное самый эффективный существующий способ занять все доступные ядра процессора. Кроме того, все асинхронные операции там выполняются пулом тредов, который не даёт создаваемым программистом задачам неконтроллируемо драться за процессор.</p>
<p>Главный минус — это работает только на одной машине. Соответственно, если вам нужно масштабировать систему на Clojure на несколько машин, это будут отдельные системы на Clojure, которые уже будут обмениваться данными не средствами языка, а так, как вы напишете.</p>
<h2>"Архаичный" Erlang</h2>
<p>Хорошо, я был не прав с этим словом — не пнул меня только ленивый :-). Вернее было бы сказать, что его синтаксис для меня слишком витиеват. Для питониста такое разнообразие знаков препинания (точек, точек с запятой, стрелочек и прочих дефисов) выглядит диковатым. Я точно знаю, что программировать можно без этого :-). Добавьте сюда ещё приписывание к имени функции числа аргументов при декларации их экспорта. Да и саму необходимость явно декларировать экспорт.</p>
<p>Откровенно говоря, я все эти вещи назвал "архаичными", потому что мне кажется очевидным, что в последнее время языки движутся в другую сторону: меньше церемоний, меньше синтаксиса как такового, меньше обязательных декларации чего бы то ни было. То есть по шкале выразительности языка от, скажем, Си до Питона, Erlang, придуманный в конце 80-х, таки кажется мне архаичным.</p>
<p>Впрочем, я не претендую хоть на какую-то объективность тут. Больше того, очень хорошо понимаю, что моё ворчание про Erlang из той же области, что ворчания не-лисперов про скобки, не-паскалистов про индексирование массивов с единицы, не-питонистов про отступы пробелами. Каждому — своё :-).</p>
<h2>Неэффективность веб-модели</h2>
<p>Ещё одна вещь, про которую я забыл. Я хочу не согласиться с Тимом Бреем, который считает, что на вебе проблемы с недостаточной параллелизацией обработки в целом решены. Да, у нас почти нет проблем с доступом к общим данным, и мы действительно обычно занимаем все доступные CPU, но только делаем это не особенно эффективно. Например мы плодим процессы/треды по количеству клиентов, которых пока что на обозримое будущее сильно больше, чем процессоров. А это значит, что процессы будут драться за ресурсы и мешать друг другу.</p>
<p>Представьте себе, если бы то же самое, что сейчас делают nginx и lighttpd, используя неблокирующий IO, удалось обобщить на все остальные вычисления. Веб-фреймворк мог бы принимать и держать у себя тысячи запросов пользователей, обрабатывая их задачи в пуле потоков, не превышающим количества процессоров.</p>
<p>Существующих библиотечных подходов типа питоньих eventlet и Twisted для этого не хватает. На них можно делать очень ээфективную асинхронность на неблокирующих IO-вызовах. Но к сожалению, кроме ввода-вывода существуют и другие вычислительные задачи.</p>
<hr>
<p>Ну вот... Кажется, всё должно стать яснее.Надо всё переписать
2019-03-06T00:12:43.041117-08:00https://softwaremaniacs.org/blog/2009/11/30/gotta-rewrite-everything/На днях Саймон Виллисон написал очень хороший пост про node.js. Саймону очень удаётся писать такие intro-посты про технологии, которые очень ясно передают самую суть вещей. Когда-то он так сподвиг меня взяться за изучение Джанго. Пост про node.js тоже хорошо даёт понять, почему это круто, и какое место в современной экосистеме ...
<p>На днях Саймон Виллисон написал очень <a href="http://blog.simonwillison.net/post/57956855516/node">хороший пост про node.js</a>. Саймону очень удаётся писать такие intro-посты про технологии, которые очень ясно передают самую суть вещей. Когда-то он так сподвиг меня взяться за изучение <a href="http://www.djangoproject.com/">Джанго</a>. Пост про <a href="http://nodejs.org/">node.js</a> тоже хорошо даёт понять, почему это круто, и какое место в современной экосистеме веба он занимает. Кто ещё не слышал про node.js — начинайте читать с этого поста.</p>
<p>Но мой пост не об этом. Просто, одной из своих фраз Саймон напомнил мне о моей собственной мысли, которая давно не даёт мне покоя, но пока всё никак не оформлялась:</p>
<blockquote>
<p>Node represents a clean slate. Twisted and EventMachine are hampered by the existence of a large number of blocking libraries for their respective languages.</p>
</blockquote>
<p>Это то, почему я без восторга смотрю например на <a href="http://www.tornadoweb.org/">Tornado</a> и на всякие задорные попытки запускать под ним ту же Джангу. Однако пост Саймона что-то вдруг повернул у меня в голове, и то, что раньше казалось мне проблемой, теперь кажется решением :-).</p>
<p>Впрочем, обо всём по порядку. (Осторожно, пост длинный!)</p>
<p><a name=more></a></p>
<h2>Две многозадачности</h2>
<p>Мне известны всего два принципиально разных способа делать многозадачность.</p>
<p>Самый распространённый способ — исполнение кода в виде нескольких процессов или тредов. Неудивительно, что он появился самым первым и стал распространён: большую часть забот по распараллеливанию исполнения программы берёт на себя платформа — ОС или runtime языка. Программист же пишет код в знакомом с детства однопоточном <em>синхронном</em> стиле. Да, конечно есть проблемы с передачей данных между процессами, и проблемы с общими данным в тредах, но мы их как-то с грехом пополам научились решать. По крайней мере в не очень сложных случаях.</p>
<p>Особенно комфортно с этим подходом в веб-программировании, где специфика позволяет вообще не общаться процессам друг с другом иначе как через БД. А это просто. И именно поэтому любой PHP-программист в состоянии написать <em>очень</em> многозадачную программу.</p>
<p>Проблема этого подхода — в производительности. Её, конечно, хватает сейчас почти на всё. Но начиная с какого-то момента многопроцессная система начинает кушать просто очень много памяти. Потому что, крути ни крути, эту память приходится копировать в каждый изолированный процесс (и copy-on-write до конца не спасает). Переключение на треды решает эту проблему, но снова не до конца. С ростом количества тредов система упирается теперь уже в резко возрастающее время простоя на локах, без которых синхронно в тредах программировать невозможно. Многим наверное знакомо типичное поведение веб-систем, запущенных в тредном режиме под нагрузкой: в какой-то момент она внезапно начинает сыпать 500-ми ошибками.</p>
<p>А хочется-то большего. Хочется <a href="http://www.kegel.com/c10k.html">десятков тысяч одновременных клиентов</a> и тысяч RPS.</p>
<p>С такими нагрузками справляются системы, написанные в асинхронном стиле. Как только мы отдаём IO на откуп ОС и не блокируем свой процесс, у нас высвобождается куча времени: ведь очень много веб-приложений ничего очень долгого и умного не делают, кроме того, что плюют небольшой 20-килобайтный кусочек HTML'а в сокет. И этого "довольно много" на самом деле настолько много, что мы в состоянии обработать тысячи клиентов <em>одним</em> процессом, просто бегая по ним в цикле. И поэтому тривиальные "hello-world'ы" на Tornado уделывают любой многопроцессный сервер.</p>
<h2>Проблема асинхронных систем</h2>
<p>Но счастья нет и здесь. Проблемы начинаются, когда приложению нет-нет, да и приходится делать что-то сколько-нибудь долгое. Например получить из БД довольно большой и неудобный кусок данных и перегруппировать его императивными средствами. Или ужать картинку. Или обратиться сходить по URL'у на чужой сервер. Или отослать email. Или распарсить большой XML.</p>
<p>Как только однопоточной программе, обслуживающей в цикле десять тысяч клиентов, приходится ради одного из них задержаться на, скажем, секундочку, эту секундочку ждут все остальные. Если таких "требовательных" набрался десяток — всё, ни один клиент не получит ответ раньше, чем через десять секунд. Решение не масштабируется на реальные задачи, как говорят у них в бизнесе.</p>
<p>Обойти это можно, только если любые долгие операции как-то так переписать, чтобы они не блокировали выполнение основной задачи. Причём, как именно это делать, в общем-то, понятно. Можно уводить их в отдельные процессы-обработчики, можно использовать асинхронный IO — всё это уже опробовано. Но штука в том, что тот код, которым мы пользуемся сейчас — наши любимые библиотеки, которые парсят нам XML и отсылают письма, <em>уже написаны в синхронном стиле</em>.</p>
<p>Причём нам тут в вебе ещё повезло, у нас вся архитектура строится вокруг модели коротеньких ответов, и именно поэтому нам до сих пор удавалось держать десятки и сотни RPS с помощью традиционной многопроцессности. А вот если мы посмотрим, например, в сторону десктопного софта, мы увидим, что там похожая проблема стоит ещё острее. Какое-то время назад хардварные ребята показали нам фигу и сказали, что им поднадоело издеваться над кремнием в поисках там ещё бо́льших гигагерц, и единственное, что они могут нам предложить теперь — это 2 (или 4, или 32) параллельных ядра, и если мы хотим, чтобы наши программы работали быстрее, нам придётся придумать, как их использовать.</p>
<p>Однако десктопный мир полон софта, который написан в абсолютно синхронном стиле под <em>один</em> процессор. И от того, что мы запустим, скажем, две копии каталогизатора фотографий, они не прочитают содержимое флешки с фотками в два раза быстрее (сходите проверьте, если не верите).</p>
<p>Все эти наблюдения приводят к одному интересному выводу.</p>
<p class=strong><strong>Если мы хотим, чтобы компьютеры работали значительно быстрее, нам придётся всё переписать в асинхронном стиле.</strong></p>
<p>И вот знаете, я теперь как-то думаю, что это не проблема, а как раз решение. Думаю я так, потому что кажется умные дядьки, у которых есть время и способности думать над такими вещами, нащупали то, как <em>именно</em> надо писать многозадачные программы, чтобы программисты при это оставались в здравом рассудке. Поэтому я, чёрт возьми, завидую следующим поколениями программистов. Потому что переписать что-нибудь с нуля мы все иной раз не прочь, но в этом случае у них будут на это реальные основания.</p>
<p>Вот вам прямо из головы задачка: асинхронный параллельный парсинг XML. Как мы сейчас парсим XML:</p>
<ol>
<li>Читаем данные из сокета. Как ни странно, это часть всей целиковой задачи по парсингу XML'а, просто по факту того, что в большинстве случаев собственно парсинг и приём случаются в одном и том же месте программы.</li>
<li>Разбиваем входной поток на лексемы</li>
<li>Обрабатываем их парсером.</li>
<li>Строим дерево.</li>
</ol>
<p>Жутко же неэффективно! Почему лексинг/парсинг уже полученного куска данных должен блокировать получение следующего куска? Почему бы лексинг разных кусков не делать в несколько потоков (map), которые потом объединять в единый парсинг (reduce), причём возможно тоже в несколько потоков? Зачем нам дерево, если в большинстве случаев пользователь всё равно будет на основе дерева составлять свои структуры — давайте придумаем <em>удобную</em> обработку элементов в SAX-стиле.</p>
<p>Всё это не так просто, но кажется очень интересным.</p>
<h2>Язык</h2>
<p>Я думаю, что большое значение имеет то, какой именно язык выбрать для глобального переписывания всего. Те объектные императивные языки, которыми мы с успехом пользуемся сейчас, кажется, не смогут выжить в новом мире, так же, как, например, C++ не стал мейнстримным языком веб-программирования, когда оно вылезло из коротких штанишек. Да, на Плюсах <em>можно</em> писать веб, никто не спорит. Но это просто неэффективно.</p>
<p>Мой любимый Питон по определению теряет здесь одно из своих больших преимуществ — большое количество написанных библиотек. Как раз потому, что именно их и надо все переписать. Зато его не сильно заметные <em>сейчас</em> недостатки — GIL и отсутствие полноценных анонимных блоков кода — магическим образом вырастают в размерах и вполне могут оказаться решающими.</p>
<p>Так чем тогда пользоваться? У меня в мыслях есть несколько кандидатов, каждый из которых имеет шансы стать Главным Языком Десятилетия. Какой выиграет, сказать не возьмусь, просто потому, что это зависит не только от качеств самих языков, но и просто от того, кому больше повезёт с хайпом, поддержкой и логотипом.</p>
<p>Пользуясь случаем, направляю вас заодно на цикл статей <a href="http://www.google.com/search?q=concur.next+site%3Atbray.org">Concur.next</a>, где Тим Брей обсуждает ту же языковую проблему существенно подробней. Собственно, я с ним согласен чуть менее, чем полностью, поэтому мысли мои здесь не оригинальны.</p>
<h3>Haskell</h3>
<p>Как многие говорят, функциональная природа языка — почти обязательное условие хорошей распараллеливаемости. Как только у вас есть неменяющиеся данные, вы, теоретически, избавляетесь от проблем с традиционным программированием с локами. Опять же теоретически, имея ленивые функции без сторонних эффектов, компилятор может в большой степени сам принимать решения о том, что и как можно параллелить. Я, впрочем, не большой знаток этого аспекта Haskell'а (да и остальных тоже :-)), поэтому могу тут здорово ошибаться.</p>
<p>В минусы языку записывается его большая сложность. Которая, в общем-то, убивает почти вся перспективу: если языком не можем пользоваться мы — туповатые инженеры — то писать и развивать библиотеки для реального мира становится некому.</p>
<h3>Erlang</h3>
<p>Как и Haskell, Erlang — язык функциональный. Но главное, там всё программирование происходит в терминах создания асинхронных процессов, и поэтому писать на нём асинхронные программы получается естественно.</p>
<p>Язык, однако, архаичен и странен. Кроме того, маленькое коммьюнити тоже не дают думать о том, что он когда-то прочно войдёт в мейнстрим. Впрочем, совсем помереть ему тоже не дадут, хотя бы потому, что <a href="http://couchdb.apache.org/">CouchDB</a> уже нужна всем, а написана как раз на Erlang.</p>
<h3>GCD + Си с блоками</h3>
<p>Apple придумала <a href="http://arstechnica.com/apple/reviews/2009/08/mac-os-x-10-6.ars/13">расширение к Си в виде анонимных блоков</a> и сделала работающий общесистемный пул тредов — GCD. А это ключевые компоненты асинхронного программирования. Си — низкоуровневый язык, компилируемый в нативный код, который работает очень быстро. Да и поддержка Apple стоит многого.</p>
<p>Главный же минус Си в том же, в чём и плюс — он низкоуровневый. А также совсем не функциональный, и без автоматического управления памятью. Вполне может оказаться, что на Си с блоками будет программировать одновременно и долго, и сложно, и багоопасно.</p>
<h3>Javascript</h3>
<p>Удивлены? Я не случайно начал пост со ссылки на пост про node.js. Одна из мыслей Саймона в том, что Javascript совсем неплох, как язык для асинхронного программирования. Анонимные блоки кода там есть. Да и не отнять, что сейчас весь ajax пишется на Javascript именно в асинхронном стиле.</p>
<p>Минусы — та же императивность и, что наверное ещё хуже, отсутствие модульности. Если что и губит этот хороший язык прямо сейчас — это общее глобальное пространство имён и отсутствие стандартного способа сделать импорт библиотеки.</p>
<h3>Clojure</h3>
<p>И вот оно — моё любимое слово последней недели (кстати, по-русски я её как-то сразу стал называть "кложура" с ударением на последний слог). Про Clojure я <a href="http://www.tbray.org/ongoing/When/200x/2009/11/18/Clojure-Parallel-I-O">узнал у того же Тима Брея</a>. Потом пошёл на сайт, посмотрел на несколько выступлений Рича Хики об этом языке, и всё — по уши в него влюблён. (В <em>язык</em>, не в Рича :-) ).</p>
<p>Clojure — это Лисп. И для меня это плюс. Когда-то я лиспами заинтересовался, и стал тем, кого "скобочки не напрягают", мне очень нравится идея code-as-data. Хотя я понимаю, что это не для всех, но вы, ребята, сами виноваты :-).</p>
<p>Однако, уж если я вменил архаичность Erlang'у, то про Лиспы можно сказать то же самое, если не в большей степени. Но Clojure — это <em>современный</em> Лисп. Там есть не только списки, но и например нативные векторы и словари (задаваемые литерально фигурными скобками). Там нет проблем с юникодом (о чём чуть позже), и там много удобных макросов. То есть это Лисп, который придумали недавно, а не в конце пятидесятых.</p>
<p>Главная же фишка Clojure — это ориентация на многопоточность. Все структуры данных там неизменяемы (да, словари тоже), что даёт возможность распараллеливать доступ к ним вообще без локов. Эти неизменяемые структуры данных умеют очень дёшево дублироваться без использования полного копирования, что делает их быстрыми. И в языке есть несколько синтаксических конструкций, позволяющих явно задавать асинхронное выполнение и транзакции для связанных изменений. Всё это обязательно стоит послушать и посмотреть в оригинале в <a href="http://blip.tv/file/812787">выступлении Рича Хики про параллельность</a>. Оно длинное, но того стоит — взрывает мозг напрочь!</p>
<p>Хотя есть у Clojure и субъективный минус: она реализована на JVM. С одной стороны это очень практично: куча библиотек, готовая инфраструктура тредов, сборки мусора и JIT-компиляции. Очень правильный юникод, опять же. Но у меня ко всей этой инфраструктуре есть субъективная нелюбовь. Несмотря на то, что JVM уже много лет, она всё ещё вызывает проблемы при установке, всё ещё занимает немеряно ресурсов, и GUI-приложения на ней всё ещё не выглядят адекватно ни в одной десктопной среде (или я ошибаюсь?). Впрочем с практической стороны это всё не так страшно, если вести речь о веб-приложениях. Там все эти потроха пользователям не видны.</p>
<p>Так что официально заявляю, что в будущей гонке за процессорное время мой фаворит — Clojure.</p>
<h2>P.S.</h2>
<p>Забавно поворачивается история, однако. Когда-то, когда Windows 95 пришла на смену Windows 3.11, одним из главных преимуществ новой архитектуры было то, что вместо кооперативной многозадачности, когда каждое приложение должно было само давать работать другим, применялась вытесняющая многозадачность, когда время на процессы жёстко распределялось системой.</p>
<p>А теперь посмотрите, куда мы пришли. Выясняется, что внешняя система не в состоянии рулить своими процессами максимально эффективно, не зная их семантики. Хочешь независимого исполнения — делай копии всей памяти на каждый процесс, даже если каждому из них нужны лишь мизерные её доли. Жалко памяти — обвешивай доступ к любым системным объектам локами. И не получится оптимизировать раздачу времени процессам так, чтобы они по возможности использовали независимые ресурсы системы. Потому что для этого надо знать, что конкретно процессы собираются сейчас делать.</p>
<p>Поэтому мы снова возвращаемся к тому, что процессы сами должны управлять своим распараллеливанием. А это — та самая кооперативная многозадачность. Хотя, конечно, существенно более сложная и безопасная, чем в Windows 3.11, и с другими языковыми средствами. Но забавно :-).