Pull to refresh

Erlang в Рисоваське, часть 1 — обзор языка

Erlang/OTP *
В этой и последующих статьях (часть 2) я хочу рассказать про язык программирования Erlang/Эрланг, его использование в нашем проекте Рисоваська, а также какие приложения и готовые модули (большинство которых тоже написаны на Эрланге) мы использовали в серверной части.

Поискав на Хабре по теме Erlang/Эрланг, понял, что тема освещена мало, есть всего пара действительно хороших статей на тему языка (например, отличная статья от создателя языка в переводе alex_blank What's all this fuss about Erlang? написанная понятным, доходчивым языком). Именно поэтому хочется остановиться сначала на самом языке и его отличиях от традиционных языков.

Виртуальная машина и ноды


Для начала хочется пояснить, что программы, написанные на Эрланге выполняются только внутри виртуальной машины, которая в терминах Эрланга называется нода (node). Есть версии виртуальной машины (или Erlang/OTP) под большинство операционных систем (Windows, Linux, Mac OS, FreeBSD, читал, что Эрланг запустили даже на iPhone). Так как ее исходники на C открыты, то компиляция под любую операционную систему не представляет проблем. На одном компьютере может быть запущено несколько нод, хотя это нужно редко. Каждая нода должна иметь свое уникальное имя, чтобы коммуницировать с другими нодами на других компьютерах сети. Если взаимодействие с другими нодами в сети не предполагается, то нода может не иметь имени. Имя ноды имеет следующий формат: «имя @ IP или название компьютера». Например, пример для локальной сети: test@192.168.0.101. Существует так же и свой компилятор эрланговских программ в родной код процессора: HiPE (high-performance native code compiler). Он входит в состав Erlang/OTP. Самое большое ускорение HiPE дает при работе с бинарными данными (почти десятикратное) и с плавающей арифметикой (foating point arithmetic), в остальных случаях прирост скорости незначительный.

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


Что же такого прикольного есть в Эрланге, чего нет в других языках и что так ломает мозг программистам на традиционных языках программирования (С, Delphi, Basic и т.д.)?

В Эрланге есть переменные, но после присвоения им значения, менять его нельзя. Что же это за переменные, скажете вы? Да, это все же переменные, так у них есть два состояния: не связанные (значение еще не присвоено) и связанные (значение присвоено). И это сделано в языке не случайно. Во-первых, это сильно упрощает сборку мусора виртуальной машине (не нужно следить за указателями). Во-вторых, позволяет легко писать алгоритмы, разбиваемые на множество отдельных процессов, не боясь, что один процесс повредит данные другого, так как они ничего не разделяют между собой.

Рекурсия


А как же мне реализовывать, например цикл, если значение переменной менять нельзя? Рекурсией! Вот простейший пример на Эрланге возведения в степень N числа X:

raising(1, _X, Result) ->
Result;
raising(N, X, Result) ->
raising(N-1, X, Result*X).


Компактно и красиво. Например raising(3, 10, 10) вернет 1000. Кстати, можете написать и raising(2000, 2, 2). Увидите очень большое число, но вычисления выполнятся без проблем. Тут скрыто еще одно интересное свойство Эрланга: он не имеет строгой типизации переменных, но об этом позже.

Рекурсия – это же плохо, скажете вы! Но не в Эрланге, если рекурсия написана, как хвостовая рекурсия (tail recursion), то есть, когда после вызова функцией самой себя больше ничего не выполняется. Пример выше, как раз пример хвостовой рекурсии: после raising(N-1, X, Amount*X) ничего нет. И тогда виртуальной машине не нужно запоминать вызовы функции в стеке. Она их сразу забывает, поэтому это работает очень быстро и нет ограничения на число вложенностей.

Процессы


Все что выполняется на ноде, является либо отдельным процессом, либо частью другого процесса. Процессы могут порождать другие процессы и следить друг за другом. Каждый процесс имеет свой уникальный номер, называемый PID. Причем, что очень важно, PID уникален не только на одной ноде, но и на всех нодах в сети! Свой собственный PID процесс может узнать вызвав функцию self(). Вот пример запуска нашей функции в качестве отдельного процесс:

Pid = spawn(?MODULE, raising, [3, 10, 10]).

Любому процессу помимо PID можно дать уникальное имя:

register(unique_name, Pid).

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

Опять же может возникнуть вопрос, что работает все это медленно. Ну уж нет! Во-первых, процессы в Эрланге не имеют никакого отношения к процессам операционной системы (у Эрланга свой планировщик процессов). На обычной средней машине простые процессы запускаются примерно со скоростью 350.000 в секунду. Может быть процессы отъедают много памяти? Совсем не много: от 4кб на простейший процесс. К тому же процессы можно усыплять (hibernating) сразу после запуска, тогда можно сократить размер памяти вообще до 1кб на один процесс.

Чтобы взаимодействовать между собой, процессы могут посылать сообщения друг другу:

Pid ! {self(), hi}.

Причем, синтаксис не меняется от того посылаешь ли сообщение процессу на той же самой ноде или на другом компьютере сети!

А вот так сообщения принимаются процессом:

receive
{Pid, hi} ->
Pid ! hello;
OtherMessage ->
Io:format(“I received some strange message: ~p~n”, [OtherMessage])
end.


Согласитесь, код отправки и приема сообщений очень компактен. В примере выше, процесс будет ждать вечно, пока не получит сообщение. Можно этого избежать, добавив в конструкцию receive блок “after N”, где N- количество миллисекунд ожидания получения сообщения.

Главные особенности


Вкратце в Эрланге:
  • Создание и разрушение процессов очень быстрое.
  • Передача сообщений между процессами очень быстрая.
  • Вы можете запустить очень много процессов (на практике запускали 20 миллионов процессов на одной ноде, в теории до 120 миллионов).
  • Процессы ничего не разделяют между собой и полностью независимы.
  • Главный способ взаимодействия между процессами: послать сообщение.

А вот и хорошая статья описывающая создание comet-сервера обслуживающего один миллион одновременных коннектов на Эрланге: http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3/ (статья состоит из трех частей). На Эрланге такая задача решается достаточно легко.

Типы


Любой переменной в Эрланге можно присвоить значение любого типа. Но так же можно и проверить какого типа значение в данной переменной, используя встроенные функции (is_integer, is_binary и т.д.). Обычно отсутствие строгой типизации в языке считается недостатком, но на практике я убедился, что чаще это преимущество и сильно повышает гибкость программы. К тому же, чтобы избежать потенциальных ошибок с типами, в состав Эрланга входит статический анализатор Dialyzer, выявляющий такого рода ошибки.

Супервизоры


А теперь перейдем к по-настоящему интересным свойствам среды разработки Erlang/OTP. Программы, написанные в Эрланге считаются обычно не просто надежными, а сверхнадежными. Как же это получается? Все просто. Все приложение написанное на Эрланге, упрощенно говоря, делится на супервизоры (supervisor – специальный процесс наблюдающий за дочерними процессами) и рабочие процессы, выполняющие основную работу. Более подробно вы можете почитать об этом в OTP Design Principles. Есть даже такое смешное понятие в Эрланге: «дай процессу умереть». И это действительно так. Если процесс находиться под супервизором, то в случае своего падения, он будет перезапущен супервизором. Вообще у супервизора можно гибко настраивать его поведение в случае падения дочернего процесса. Например, он может в такой ситуации остановить все дочерние процессы и запустить их все заново. Это нужно в ситуации, когда дочерние процессы, как-либо зависят друг от друга и падение одного, может привести к неработоспособности остальных.

Таким образом программа построенная на принципах OTP будет выглядеть в виде дерева процессов:
Дерево процессов
Рисунок взят из OTP Design Principles

Учитывая, что супервизоры не выполняют никаких вычислений, а только наблюдают, то вероятность их падения стремиться к нулю. Конечно же может упасть вся нода целиком, например из-за нехватки памяти, но и тут есть решение: можно запустить специальный процесс операционной системы, наблюдающий за нодой и перезапускающий ее через определенное время. Есть и другой вариант: распределенные приложения (Distributed Applications) – это такие приложения которые могут работать на нескольких нодах. Причем в один момент времени, приложение работает только на одной ноде. В случае падения ноды, на которой работает такое приложение, оно автоматически перезапускается на следующей ноде в списке. Список нод, где может работать распределенное приложение можно менять динамически в процессе работы.

Работа с бинарными данными


У Эрланга действительно быстрая работа с бинарными данными (особенно в связке с HiPE), поэтому на нем очень естественно писать обработку входных бинарных данных, вплоть до работы с отдельными битами. Тут в сравнении с Java 6 Эрланг выигрывает в несколько раз по скорости работы.

Недостатки


Статья была бы не полной, если не сказать и о основных недостатках Эрланга:
  • отсутствие поддержки Unicode строк (всеми избитый недостаток языка, правда уже этой весной обещают добавить их поддержку в Erlang R13B; ждем),
  • медленная математика, писать на нем какие-то серъезные математические расчеты неэффективно,
  • небольшое кол-во дополнительных библиотек (хотя все самое необходимое присутствует и число библиотек постоянно растет все же еще далеко до такого разнообразия, как в .NET или C),
  • отсутствие отлаженной и быстрой графической библиотеки, поэтому писать клиенские приложения на Ерланге я бы не стал (конечно есть, например, wxErlang, но библиотека еще далека до завершения).

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

Продолжение следует


На этом я пожалуй закончу первую статью, она и так уже получается достаточно большая. В следующих статьях я расскажу про использование распределенной базы Mnesia (которая входит в Erlang/OTP), использование Amazon S3 и Amazon EC2.

Пишите, плз, в комментариях к этой статье, о каких особенностях языка хотелось бы узнать более подробно.

Продолжение цикла статей — часть 2.
Tags:
Hubs:
Total votes 75: ↑71 and ↓4 +67
Views 9.7K
Comments Comments 68